为什么 making operator new private break std::shared_ptr?

Why does making operator new private break std::shared_ptr?

我正在实现一个多态类型(称之为 A),我想通过 std::shared_ptr 专门管理它。为了允许在派生的 classes 的构造函数中使用 shared_from_this,使用 new 分配 A,然后将成员 std::shared_ptr 初始化为自身以自动管理其生命周期.为了帮助实施这一点,我决定将 class-specific 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

编辑:是的,在实际代码中,class 确实派生自 std::enable_shared_from_this,但没有必要重现错误。

您禁用了 "anyone but class A" 创建和销毁 class A 的对象。这意味着 class A 的临时对象无法在其他任何地方创建。

很可能是你"broke" 模板的 SFINAE 模式创建了时间对象。

shared_ptr构造函数接受分配器和删除器,它们可以封装一些合法的方式来删除你的对象

当我尝试你的代码时,我得到了一个完全不同的错误:

error C2248: 'A::operator delete': cannot access private member declared in class 'A'

罪魁祸首是这个

m_sharedthis(this)

您提供了指针,但没有删除器。所以 std::shared_ptr 将尝试使用默认的删除器,它可能包含你的类型的 delete 表达式。由于该删除器与您的 class 无关,因此它无法访问私有 operator delete.

解决方法是提供自定义删除器

m_sharedthis(this, [](A* a) {delete a;})

已在 class 范围内定义的 lambda 在其定义点可以访问 operator delete

在一个不相关的笔记上。您编写的代码将泄漏所有这些对象。由于对象都持有对自身的强引用,它们如何达到引用计数为零?考虑改用 std:enable_shared_from_this

神秘的 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 声明:

This constructor additionally does not participate in overload resolution if the delete expression is not well-formed.

这使得各种复制和移动构造函数成为唯一的单参数构造函数,编译器选择移动构造函数作为最接近的匹配但不能将原始指针转换为 shared_ptr<T>&&nullptr_t 错误消息的一部分是因为编译器试图将其用作中间步骤,因为它可以转换为 shared_ptr<T>&&.

您需要提供自定义删除器来解决此问题:

m_sharedthis(this, [](A* a) {delete a;})

正如其他人所指出的,您的对象将永远不会被删除,因为它们包含对自身的强引用。您应该将 m_sharedthis 更改为 std::weak_ptr 或使用 std::shared_from_this.