为什么要使运算符新的私有中断std :: shared_ptr?

时间:2018-09-13 05:50:16

标签: c++ c++11 shared-ptr

我正在实现一个多态类型(称为A),我希望通过std::shared_ptr对其进行独占管理。为了允许在派生类的构造函数中使用shared_from_this,使用A分配new,然后向其自身初始化成员std::shared_ptr以自动管理其生存期。为了帮助实现这一点,我决定将特定于类的operator new设为私有,并计划使用create()辅助函数代替newmake_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派生的,但是没有必要重现该错误。

3 个答案:

答案 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构造函数接受分配器和删除器,它们可以封装一些删除对象的合法方法