Return std::unique_ptr<T> 从工厂函数创建纯虚拟接口的完全隐藏实现
Return std::unique_ptr<T> from factory function creating fully hidden implementation of pure virtual interface
我正在阅读 boost 文档中提供的 Smart Pointer Programming Techniques。
在“using abstract classes for implementation hiding”部分,他们提供了一个很好的习惯用法来完全隐藏纯虚拟接口背后的实现。例如:
// Foo.hpp
#include <memory>
class Foo {
public:
virtual void Execute() const = 0;
protected:
~Foo() = default;
};
std::shared_ptr<const Foo> MakeFoo();
和
// Foo.cpp
#include "Foo.hpp"
#include <iostream>
class FooImp final
: public Foo {
public:
FooImp() = default;
FooImp(const FooImp&) = delete;
FooImp& operator=(const FooImp&) = delete;
void Execute() const override {
std::cout << "Foo::Execute()" << std::endl;
}
};
std::shared_ptr<const Foo> MakeFoo() {
return std::make_shared<const FooImp>();
}
关于 class Foo
中受保护的非虚拟析构函数,文档指出:
Note the protected and nonvirtual destructor in the example above. The
client code cannot, and does not need to, delete a pointer to X
; the
shared_ptr<X>
instance returned from createX
will correctly call
~X_impl
.
我相信我明白了。
现在,在我看来,如果工厂函数返回 std::unique_ptr<Foo>
,这个漂亮的习惯用法可以用来生成类似单例的实体;用户将被迫 move
指针,编译时保证不存在任何副本。
但是,唉,除非我将 ~Foo() = default
从 protected
更改为 public
,否则我无法使代码工作,我不明白为什么。
换句话说,这是行不通的:
std::unique_ptr<const Foo> MakeUniqueFoo() {
return std::make_unique<const FooImp>();
}
我的问题:
- 你能解释一下为什么我需要制作
public
~Foo() = default
吗?
- 只删除
protected
会很危险吗?
- 单例类的想法值得吗?
问题与删除器在智能指针中的工作方式有关。
在shared_ptr
中,删除器是动态的。当你有std::make_shared<const FooImp>();
时,那个对象中的删除器会直接调用~FooImpl()
:
The object is destroyed using delete-expression or a custom deleter that is supplied to shared_ptr during construction.
该删除器将在创建时复制到 shared_ptr<const Foo>
。
在unique_ptr
中,删除器是类型的一部分。它是:
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
所以当你有 unique_ptr<const Foo>
时,将直接调用 ~Foo()
- 这是不可能的,因为 ~Foo()
是 protected
。这就是为什么当您制作 Foo()
public 时,它会起作用。工程,如编译。您也必须使其成为 virtual
- 否则您将通过仅破坏 FooImpl
.
的 Foo
部分来获得未定义的行为
不危险。除非您忘记将析构函数设为虚拟,否则需要重复,这将导致未定义的行为。
这不是真正的单例。至于值不值?主要基于意见。
根据 Barry 的回答,public 的另一种选择是定义您自己的删除器,它可以访问您的 class' ~Foo()
方法。
示例(尝试使用 VS2013):
template <typename T>
class deleter
{
public:
void operator()(T* a)
{
// Explicitly call the destructor on a.
a->~A();
}
};
class A {
friend class deleter<A>; // Grant access to the deleter.
protected:
~A() {
// Destructor.
}
};
std::unique_ptr<A, deleter<A>> MakeA()
{
return std::unique_ptr<A, deleter<A>>(new A());
}
每个shared_ptr
存储4个东西:指针、强引用计数、弱引用计数和删除器。
删除器获取构造 shared_ptr
的类型,并删除 那个 类型,而不是公开的类型。如果将其转换为基数 shared_ptr
,派生删除器仍会存储。
unique_ptr
默认情况下不存储这种有状态的删除器。
这背后的设计原因是 shared_ptr
已经在管理额外的资源:考虑到您已经在管理引用计数,添加删除器的成本很低。
对于unique_ptr
,没有状态删除器,它的开销基本上与原始指针相同。默认情况下添加有状态删除器会使 unique_ptr
s 显着增加成本。
虽然它们都是智能指针,但 unique_ptr
确实是最小的,而 shared_ptr
则复杂得多。
您可以通过向 unique_ptr
添加一个有状态的删除器来解决这个问题。
struct stateful_delete {
void const* ptr = nullptr;
void(*f)(void const*) = nullptr;
template<class T>
stateful_delete(T const* t):
ptr(t),
f([](void const* ptr){
delete static_cast<T const*>(ptr);
})
{}
template<class T>
void operator()(T*)const{
if (f) f(ptr);
}
};
template<class T>
using unique_ptr_2 = std::unique_ptr<T, stateful_delete>;
template<class T>
unique_ptr_2<T> unique_wrap_2(T* t) {
return {t, t};
}
template<class T, class...Args>
unique_ptr_2<T> make_unique_2(Args&&...args) {
return unique_wrap( new T(std::forward<Args>(args)...) );
}
这样的 unique_ptr_2
是 unique_ptr
的 3 倍。他们不进行额外分配(与 shared_ptr
不同)。他们将使用 public ~FooImpl
.
的非虚拟保护 ~Foo
如果我们使用 make_shared
技术进行统一分配,您可以将 unique_ptr_2
的大小减少到 2 个指针,并存储 ptr
和 f
在堆上。我不确定这种复杂性是否值得节省。
我正在阅读 boost 文档中提供的 Smart Pointer Programming Techniques。
在“using abstract classes for implementation hiding”部分,他们提供了一个很好的习惯用法来完全隐藏纯虚拟接口背后的实现。例如:
// Foo.hpp
#include <memory>
class Foo {
public:
virtual void Execute() const = 0;
protected:
~Foo() = default;
};
std::shared_ptr<const Foo> MakeFoo();
和
// Foo.cpp
#include "Foo.hpp"
#include <iostream>
class FooImp final
: public Foo {
public:
FooImp() = default;
FooImp(const FooImp&) = delete;
FooImp& operator=(const FooImp&) = delete;
void Execute() const override {
std::cout << "Foo::Execute()" << std::endl;
}
};
std::shared_ptr<const Foo> MakeFoo() {
return std::make_shared<const FooImp>();
}
关于 class Foo
中受保护的非虚拟析构函数,文档指出:
Note the protected and nonvirtual destructor in the example above. The client code cannot, and does not need to, delete a pointer to
X
; theshared_ptr<X>
instance returned fromcreateX
will correctly call~X_impl
.
我相信我明白了。
现在,在我看来,如果工厂函数返回 std::unique_ptr<Foo>
,这个漂亮的习惯用法可以用来生成类似单例的实体;用户将被迫 move
指针,编译时保证不存在任何副本。
但是,唉,除非我将 ~Foo() = default
从 protected
更改为 public
,否则我无法使代码工作,我不明白为什么。
换句话说,这是行不通的:
std::unique_ptr<const Foo> MakeUniqueFoo() {
return std::make_unique<const FooImp>();
}
我的问题:
- 你能解释一下为什么我需要制作
public
~Foo() = default
吗? - 只删除
protected
会很危险吗? - 单例类的想法值得吗?
问题与删除器在智能指针中的工作方式有关。
在
shared_ptr
中,删除器是动态的。当你有std::make_shared<const FooImp>();
时,那个对象中的删除器会直接调用~FooImpl()
:The object is destroyed using delete-expression or a custom deleter that is supplied to shared_ptr during construction.
该删除器将在创建时复制到
shared_ptr<const Foo>
。在
unique_ptr
中,删除器是类型的一部分。它是:template< class T, class Deleter = std::default_delete<T> > class unique_ptr;
所以当你有
unique_ptr<const Foo>
时,将直接调用~Foo()
- 这是不可能的,因为~Foo()
是protected
。这就是为什么当您制作Foo()
public 时,它会起作用。工程,如编译。您也必须使其成为virtual
- 否则您将通过仅破坏FooImpl
. 的 不危险。除非您忘记将析构函数设为虚拟,否则需要重复,这将导致未定义的行为。
这不是真正的单例。至于值不值?主要基于意见。
Foo
部分来获得未定义的行为
根据 Barry 的回答,public 的另一种选择是定义您自己的删除器,它可以访问您的 class' ~Foo()
方法。
示例(尝试使用 VS2013):
template <typename T>
class deleter
{
public:
void operator()(T* a)
{
// Explicitly call the destructor on a.
a->~A();
}
};
class A {
friend class deleter<A>; // Grant access to the deleter.
protected:
~A() {
// Destructor.
}
};
std::unique_ptr<A, deleter<A>> MakeA()
{
return std::unique_ptr<A, deleter<A>>(new A());
}
每个shared_ptr
存储4个东西:指针、强引用计数、弱引用计数和删除器。
删除器获取构造 shared_ptr
的类型,并删除 那个 类型,而不是公开的类型。如果将其转换为基数 shared_ptr
,派生删除器仍会存储。
unique_ptr
默认情况下不存储这种有状态的删除器。
这背后的设计原因是 shared_ptr
已经在管理额外的资源:考虑到您已经在管理引用计数,添加删除器的成本很低。
对于unique_ptr
,没有状态删除器,它的开销基本上与原始指针相同。默认情况下添加有状态删除器会使 unique_ptr
s 显着增加成本。
虽然它们都是智能指针,但 unique_ptr
确实是最小的,而 shared_ptr
则复杂得多。
您可以通过向 unique_ptr
添加一个有状态的删除器来解决这个问题。
struct stateful_delete {
void const* ptr = nullptr;
void(*f)(void const*) = nullptr;
template<class T>
stateful_delete(T const* t):
ptr(t),
f([](void const* ptr){
delete static_cast<T const*>(ptr);
})
{}
template<class T>
void operator()(T*)const{
if (f) f(ptr);
}
};
template<class T>
using unique_ptr_2 = std::unique_ptr<T, stateful_delete>;
template<class T>
unique_ptr_2<T> unique_wrap_2(T* t) {
return {t, t};
}
template<class T, class...Args>
unique_ptr_2<T> make_unique_2(Args&&...args) {
return unique_wrap( new T(std::forward<Args>(args)...) );
}
这样的 unique_ptr_2
是 unique_ptr
的 3 倍。他们不进行额外分配(与 shared_ptr
不同)。他们将使用 public ~FooImpl
.
~Foo
如果我们使用 make_shared
技术进行统一分配,您可以将 unique_ptr_2
的大小减少到 2 个指针,并存储 ptr
和 f
在堆上。我不确定这种复杂性是否值得节省。