【问题标题】:Can I workaround unique_ptr<MyType> to not need MyType destructor definition when only storing nullptr?我可以解决 unique_ptr<MyType> 在仅存储 nullptr 时不需要 MyType 析构函数定义吗?
【发布时间】:2019-10-22 19:44:00
【问题描述】:

我需要使用VS2012编译器并且有:

virtual std::unique_ptr<MyType> pass_through(std::unique_ptr<MyType> instance) override { return std::unique_ptr<MyType>(nullptr); };

该定义仅作为存根存在于项目中,并且没有 MyType 析构函数,我收到以下错误:

错误 LNK2001:无法解析的外部符号“public: __thiscall MyType::~MyType(void)”(??1MyType@@QAE@XZ)

所以我创建了一个定义:

MyType::~MyType() {}

这就是问题所在,我不想让上面的函数通过构建而混淆定义。所以有没有办法不需要指定析构函数定义并且仍然具有该 pass_through 方法的有效实现?

也许我可以以某种方式更改方法的签名,或者它的逻辑在主要实现中基本相同,它执行以下操作:

std::unique_ptr<MyType> pass_through(std::unique_ptr<MyType> instance)
{
    if (!instance) {
        instance= std::unique_ptr<MyType>(new MyType(/*arguments*/));
    }
    instance->something();
    return instance;
}

顺便说一句,我看到类似的问题被否决/关闭,但仍然在建议中我没有看到任何相关的答案,而且我之前也使用过谷歌,但仍然没有命中 => 可能以某种方式用好的答案推广相关问题,如果有的话有吗?

【问题讨论】:

  • 你可以把它声明为默认值~MyType() = default
  • 谢谢,我的意思是我根本不想有定义。但这是改进。 “~MyType() = delete;”怎么样?然后?当我不需要析构函数时会更好。
  • 当你想拥有该类的实例时,你不能没有析构函数。它将如何被摧毁?
  • 我个人认为没有你声明的析构函数的定义更令人困惑。如果删除析构函数,则永远无法创建该类的实例。
  • 啊哈,我的声明没有问题,但是定义,在有问题的编译单元中,我不能使用“= default;”,因为声明是共享的,定义不是在其他编译单元中微不足道。

标签: c++ c++11 destructor definition interface-implementation


【解决方案1】:

我认为一个问题有几个方面。 首先是理解 unique_ptr 做了什么:

它将以 RAII 方式包装你的类,持有指向你的类的指针,并在被破坏或重新分配时释放它。为了确保我们在同一页面上,让我们探索 unique_ptr 的示例实现(取自 this 问题):

template<typename T>
class unique_ptr {
private:
    T* _ptr;
public:
    unique_ptr(T& t) {
       _ptr = &t;
    }
    unique_ptr(unique_ptr<T>&& uptr) {
       _ptr = std::move(uptr._ptr);
       uptr._ptr = nullptr;
    }
    ~unique_ptr() {
       delete _ptr;
    }
    unique_ptr<T>& operator=(unique_ptr<T>&& uptr) {
       if (this == uptr) return *this;
       _ptr = std::move(uptr._ptr);
       uptr._ptr = nullptr;
       return *this;
    }

    unique_ptr(const unique_ptr<T>& uptr) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>& uptr) = delete;
};

如你所见,唯一指针析构函数在 ~unique_ptr() 函数中对实际对象调用 delete。

现在让我们看看标准:

3.7.4 动态存储持续时间 [basic.stc.dynamic] 1 可以在程序执行期间动态创建对象(1.9),使用 new-expressions (5.3.4),并使用 delete-expressions 销毁 (5.3.5)。 C++ 实现提供访问和管理, 通过全局分配函数 operator new 和 operator new[] 和全局释放函数 operator delete 和运算符 delete[]。

还有:

如果删除表达式的操作数的值不为空 指针值,删除表达式将调用析构函数(如果 any) 用于被删除的对象或数组的元素。在里面 数组的情况下,元素将按顺序销毁 递减地址(即按完成的相反顺序 他们的构造函数;见 12.6.2)。

现在鉴于这是标准强制执行的行为,您需要声明和定义一个析构函数。

定义,即使是空的,也需要告诉链接器在对象被删除的情况下跳转到哪里(或者它什么也不做)。

因此,总而言之,您需要自己声明和定义析构函数,依靠自动创建的默认析构函数(甚至不声明它)或将其声明为默认值。 (比如说你定义它的编译器,但它应该有一个默认的实现)。

【讨论】:

    【解决方案2】:

    您不需要在同一个编译单元中定义。我认为你链接错误。 (包含void MyType::something() { /* ... */ }的.cpp文件也应该有MyType::~MyType() { /* ... */ },所以如果你使用(MyType*)-&gt;something,你应该可以使用析构函数。

    如果这确实是您想要做的(编译程序的一小部分而不将单元与析构函数链接,无论出于何种原因),默认的std::unique_ptr&lt;T&gt; 是不可能的,它需要调用@987654325 @,最终需要析构函数可用。

    您可以使用自定义删除器,在特殊情况下您可以使用“什么都不做”删除器。这意味着您不需要MyType::~MyType 符号:

    #include <memory>
    
    class MyType {
    public:
        void something();
    
        ~MyType();
    };
    
    // `MyUniquePtr` calls a stored function pointer to delete it's value
    using MyUniquePtr = std::unique_ptr<MyType, void(*)(const MyType*) noexcept>;
    
    void MyTypeDeleter(const MyType* p) noexcept;
    
    // This is compatible with `MyUniquePtr`'s deleter, and does nothing when called
    void MyTypeNullDeleter(const MyType* p) noexcept {
        // For extra safety add this check
        if (p != nullptr) std::terminate();
    
        // Otherwise do nothing
        // (Don't need to delete a nullptr, don't need a destructor symbol)
    }
    
    MyUniquePtr pass_through(MyUniquePtr instance) {
        return MyUniquePtr(nullptr, MyTypeNullDeleter);
    };
    
    // In a seperate unit where the destructor is defined, you still
    // have to use the `MyUniquePtr` type for the virtual functions,
    // but need a deleter which actually deletes the pointer.
    // That's what `MyTypeDeleter` is.
    
    MyType::~MyType() { }
    
    void MyTypeDeleter(const MyType* p) noexcept {
        delete p;
    }
    
    MyUniquePtr pass_through2(MyUniquePtr instance) {
        if (!instance) {
            instance = MyUniquePtr(new MyType(/*arguments*/), MyTypeDeleter);
        }
        instance->something();
        return instance;
    }
    

    但是很容易意外地将“什么都不做”删除器分配给非空指针。

    【讨论】:

      【解决方案3】:

      我只能想到一个可能会出现问题的场景。那就是你声明一个析构函数但不定义它的时候:

      struct X { ~X(); };
      
      int main() {
        std::unique_ptr<X> p(new X); 
      }
      

      这会触发链接器错误。那么为什么不干脆省略析构函数声明呢?

      struct X { };
      

      现在,一切正常。


      请注意,如果析构函数被删除(手动或由编译器,例如,通过从不可破坏的基类派生),那么,您将收到 compile-time 错误,而不是链接时间 一:

      struct X { ~X() = delete; };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-10
        相关资源
        最近更新 更多