我正在实现一个多态类型(称为A
),我希望通过std::shared_ptr
对其进行独占管理。为了允许在派生类的构造函数中使用shared_from_this
,使用A
分配new
,然后向其自身初始化成员std::shared_ptr
以自动管理其生存期。为了帮助实现这一点,我决定将特定于类的operator new
设为私有,并计划使用create()
辅助函数代替new
和make_shared
。该设计可能看起来有些有趣,但是在上下文中对于我正在处理的UI库来说是有意义的。最小的可重现示例如下:
struct A {
A() : m_sharedthis(this) {
}
void destroy(){
m_sharedthis = nullptr;
}
std::shared_ptr<A> self() const {
return m_sharedthis;
}
private:
friend std::shared_ptr<A> create();
std::shared_ptr<A> m_sharedthis;
};
std::shared_ptr<A> create(){
auto a = new A();
return a->self();
}
这可以编译并正常工作。当我将以下代码添加到A
的正文中时,就会出现问题:
struct A {
...
private:
void* operator new(size_t size){
return ::operator new(size);
}
void operator delete(void* ptr){
::operator delete(ptr);
}
...
};
现在,此操作无法在MSVC 2017上编译,并显示了一个隐秘的错误消息:
error C2664: 'std::shared_ptr<A>::shared_ptr(std::shared_ptr<A> &&) noexcept': cannot convert argument 1 from 'A *' to 'std::nullptr_t'
note: nullptr can only be converted to pointer or handle types
这是怎么回事?为什么std::shared_ptr
构造函数试图突然只接受nullptr
?
编辑:是的,在实际的代码中,该类确实是从std::enable_shared_from_this
派生的,但是没有必要重现该错误。
答案 0 :(得分:12)
当我尝试您的代码时,出现了一个完全不同的错误:
error C2248: 'A::operator delete': cannot access private member declared in class 'A'
罪魁祸首是这个
m_sharedthis(this)
您提供了一个指针,但没有删除器。因此,std::shared_ptr
将尝试使用默认的删除程序,该删除程序可能在您的类型上包含一个delete
表达式。由于该删除程序与您的课程无关,因此无法访问专用的operator delete
。
一种解决方法是提供自定义删除器
m_sharedthis(this, [](A* a) {delete a;})
已在类范围内定义的lambda可以在其定义时访问operator delete
。
无关的注释。您编写的代码将泄漏所有这些对象。由于所有对象都对自己有强烈的引用,因此它们将如何达到零的引用计数?考虑改用std:enable_shared_from_this
。
答案 1 :(得分:5)
Visual Studio的神秘错误是由shared_ptr
构造函数引起的,该构造函数仅将指针用于不可删除的类型而禁用:
template<class _Ux,
enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,
_SP_convertible<_Ux, _Ty>>, int> = 0>
explicit shared_ptr(_Ux * _Px)
由于c ++ 17状态,构造函数被禁用:
如果delete表达式的格式不正确,则此构造方法将不参与重载解析。
这会将各种复制和移动构造函数保留为唯一的单参数构造函数,编译器选择移动构造函数作为最接近的匹配项,但无法将原始指针转换为shared_ptr<T>&&
(nullptr_t
部分)错误消息的原因是因为编译器试图将其用作中间步骤,因为它可以转换为shared_ptr<T>&&
。
您需要提供一个自定义删除器来解决此问题:
m_sharedthis(this, [](A* a) {delete a;})
正如其他人所指出的那样,您的对象将永远不会被删除,因为它们包含对自己的强烈引用。您应该将m_sharedthis
更改为std::weak_ptr
或使用std::shared_from_this
。
答案 2 :(得分:0)
您禁用了“除A类之外的任何人”来创建和销毁A类的对象。这意味着无法在其他任何地方创建A类的临时对象。
创建临时对象很可能会“破坏”模板的SFINAE模式。
shared_ptr构造函数接受分配器和删除器,它们可以封装一些删除对象的合法方法