3

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

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

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

错误 LNK2001:未解析的外部符号“公共:__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;
}

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

4

3 回答 3

1

我认为一个问题有几个方面。首先是了解 unique_ptr 的作用:

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

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 和 operator delete[] 提供对动态存储的访问和管理。

还:

如果 delete-expression 的操作数的值不是空指针值,则 delete-expression 将为要删除的对象或数组的元素调用析构函数(如果有)。在数组的情况下,元素将按照地址递减的顺序被销毁(即,按照它们的构造函数完成的相反顺序;参见 12.6.2)。

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

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

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

于 2019-06-07T10:57:05.230 回答
0

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

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

您可以使用自定义删除器,在特殊情况下您可以使用“什么都不做”删除器。这意味着您不需要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;
}

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

于 2019-06-07T10:37:48.260 回答
0

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

struct X { ~X(); };

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

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

struct X { };

现在,一切正常。


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

struct X { ~X() = delete; };
于 2019-06-07T11:35:22.577 回答