std::unique_ptr
有2个模板参数,第二个是要使用的删除器。由于这个事实,可以通过以下方式轻松地将unique_ptr
替换为类型,这需要自定义删除(例如SDL_Texture
):
using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>;
...其中SDL2PtrDeleter
是一个用作删除器的仿函数。
鉴于这个别名,程序员能够构建和重置SDL_TexturePtr
而无需关心或甚至不知道自定义删除器:
SDL_TexturePtr ptexture(SDL_CreateTexture(/*args*/));
//...
ptexture.reset(SDL_CreateTexture(/*args*/));
另一方面, std::shared_ptr
没有模板参数,这允许将删除器指定为类型的一部分,因此以下是非法的:
// error: wrong number of template arguments (2, should be 1)
using SDL_TextureSharedPtr = shared_ptr<SDL_Texture, SDL2PtrDeleter>;
因此,对类型别名最好的是:
using SDL_TextureSharedPtr = shared_ptr<SDL_Texture>;
但与明确使用shared_ptr<SDL_Texture>
相比,这几乎没有什么优势,因为用户必须知道要使用的删除函数,并在每次构造或重置SDL_TextureSharedPtr
时指定它:
SDL_TextureSharedPtr ptexture(SDL_CreateTexture(/*args*/), SDL_DestroyTexture);
//...
ptexture.reset(SDL_CreateTexture(/*args*/), SDL_DestroyTexture);
从上面的示例中可以看出,用户需要知道删除SDL_Texture
(SDL_DestroyTexture()
)的正确函数,并且每次都要传递指针。除了不方便之外,程序员可能会通过将不正确的函数指定为删除程序来引入错误。
我想以某种方式将删除器封装在共享指针本身的类型中。由于我无法通过使用类型别名来实现这一点,因此我考虑了3个选项:
创建一个包装std::shared_ptr<T>
的类,它将复制shared_ptr
的接口,但允许通过自己的模板参数指定删除器仿函数。然后,当从其自己的构造函数或operator()
方法调用其底层reset()
实例的构造函数或std::shared_ptr<T>
方法时,此包装器将提供指向其删除器实例reset()
的指针。 , 分别。当然,缺点是std::shared_ptr
的整个,相当大的接口必须在这个包装类中重复,这是WET。
创建std::shared_ptr<T>
的子类,允许通过自己的模板参数指定删除函子。假设public
继承,这将帮助我们避免复制shared_ptr
接口的需要,但会打开一堆自己的蠕虫。即使std::shared_ptr
不是final
,它似乎也没有被设计为子类,因为它具有非虚拟析构函数(尽管在这种特殊情况下这不是问题) 。更糟糕的是,reset()
中的shared_ptr
方法不是虚拟的,因此无法覆盖 - 只会被遮蔽,从而为错误使用打开了大门:使用public
继承,用户可能会将对我们子类的实例的引用传递给某个API,接受std::shared_ptr<T>&
,其实现可能会调用reset()
,完全绕过我们的方法。对于非公共继承,我们与选项#1相同。
对于上述两个选项,最后,SDL_TextureSharedPtr
可以表示如下,假设MySharedPtr<T, Deleter>
是我们的(子)类:
using SDL_TextureSharedPtr = MySharedPtr<SDL_Texture, SDL2PtrDeleter>;
std::default_delete<T>
。这是基于我的错误假设std::shared_ptr<T>
使用std::default_delete<T>
,如unique_ptr
那样,如果没有明确提供删除器。这是不的情况。感谢@DieterLücking
指出这一点! 鉴于以上这些选项和推理,这是我的问题。
我是否错过了一种更简单的方法,可以避免每次构建实例时std::shared_ptr<T>
都指定删除器reset()
?
如果没有,我的推理是否正确我列出的选项?还有其他客观理由偏好这些选项中的一种而不是另一种吗?
答案 0 :(得分:4)
using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>;
鉴于这个别名,程序员能够构建和重置
SDL_TexturePtr
而无需关心或甚至不知道自定义删除器:
嗯,这是一种(通常是致命的)过度简化。相反,iff默认构造的删除器适合于构造,删除器的当前值分别适合于重置指针,而不需要手动更改。
您对包装或扩展shared_ptr
所发现的缺点是正确的,尽管有些人可能会说它允许您添加新的实例方法。
你应该尽量减少耦合,这意味着更喜欢自由功能,因为你不需要比现有的公共接口更多来编写它们。
如果没有指定删除器会导致使用std::default_delete
(遗憾的是它不会)并且每种类型只需要一个删除器,或者标准的delete-expression适合您的用例(它似乎没有),第三种选择是你可以选择的最佳选择。
因此,一个不同的选择:
使用构造函数来抽象出(可能很复杂的)构造和自定义删除器
这样你只能写一次,自由使用auto
可以进一步减轻你的头痛。
答案 1 :(得分:2)
作为一个选项,您没有包含大量using
指令的私有继承,以显示未更改的功能。
它比使用私有副本重写共享ptr更简单,但允许你编写一个没有暴露危险的自定义reset
。
另请注意,共享ptr具有来自唯一ptr的转换ctor。如果您的工厂函数创建了唯一的ptrs,则可以根据需要将它们分配给共享的ptrs,并且使用正确的删除器。消除代码中的原始指针,重置问题就消失了。
答案 2 :(得分:2)
您应该可以使用veneer:
// A shared_ptr which will use SDL2PtrDeleter **by default**:
class SharedTexure : public std::shared_ptr<SDL_Texture> {
public:
constexpr SharedTexure() : std::shared_ptr<SDL_Texture>() {}
constexpr SharedTexure(std::nullptr_t) : std::shared_ptr<SDL_Texture>() {}
explicit SharedTexure(SDL_Texture* texture) :
std::shared_ptr<SDL_Texture>(texture, SDL2PtrDeleter()) {}
SharedTexture(std::shared_ptr<SDL_Texture> texture) :
std::shared_ptr<SDL_Texture>(std::move(texture)) {}
};
可以概括为:
template<class T, class D>
class shared_ptr : public std::shared_ptr<T> {
public:
constexpr shared_ptr() : std::shared_ptr<T>() {}
constexpr shared_ptr(std::nullptr_t) : std::shared_ptr<T>() {}
template<class U>
explicit shared_ptr(U* ptr) :
std::shared_ptr<T>(ptr, D()) {}
template<class U>
shared_ptr(std::shared_ptr<U> ptr) :
std::shared_ptr<T>(std::move(ptr)) {}
};
using SharedTexure = shared_ptr<SDL_Texture, SDL2PtrDeleter>;
你应该能够继承构造函数:
template<class T, class D>
class shared_ptr : public std::shared_ptr<T> {
public:
using std::shared_ptr<T>::shared_ptr;
template<class U>
explicit shared_ptr(U* ptr) :
std::shared_ptr<T>(ptr, D()) {}
};
它似乎没有设计为子类, 因为它有一个非虚拟析构函数
referenced paper表示此用例是安全的。然而,从标准中获得合适的参考资料会很有趣。
答案 3 :(得分:1)
你太过于把删除器放在类型本身。而是关注shared_ptr
实例的来源。
解决此问题的最有效方法是正确地集中引入此系统shared_ptr
的位置。应该有一个生成它们的功能;它负责附加适当的删除者。
显然,这样的系统不能保证。但是,如果你从不使用shared_ptr::reset
(实际上,没有理由这样做)并且你从不直接构造一个(复制/移动很好,但其他构造函数不是),那么你就是安全。如果您需要将shared_ptr
重新分配给新实例,请使用operator=
;这就是它的用途。
最终,这与自由使用make_shared
。