c++ - std::unique_ptr 虚函数模板类中的不完整类型错误

标签 c++ templates

我有一个小问题,我现在不明白,也找不到解释。我阅读了有关如何在 PIMPL 惯用语中使用 std::unique_ptr 的信息,它可以工作,但..不是在一种奇怪的情况下,我现在当然会想到这种情况。

最简单的 - 我将展示一个重现问题的简化代码示例(使用 VS2017 社区编译)。

header.h ##

Forward类的前向声明,以及具有返回unique_ptr的虚函数的模板类TestForward。

class Forward;
using TestUniquePtr = std::unique_ptr<Forward>;
TestUniquePtr make_ptr();

template<int a>
class TestForward {

public:
    virtual TestUniquePtr foo();
};

template<int a>
TestUniquePtr TestForward<a>::foo() {
    return make_ptr();
}

转发.h

#include "header.h"
#include <iostream>

class Forward {
public:
    ~Forward() {
        std::cout << "HAAA" << std::endl;
    }
};

转发.cpp

#include "forward.h"

TestUniquePtr make_ptr() {
    return TestUniquePtr{ new Forward };
}

main.cpp

由于“无法删除不完整的类型”而无法编译的文件。 请注意,这里甚至没有调用函数 foo。 那么编译器应该尝试在这个单元中编译这个函数吗? 如果此函数不是虚拟函数或 TestForward 不是模板 - 它可以工作。

#include "header.h"

int main (int argc, char *argv[]) {
    TestForward<3> a;
    return 0;
}

我知道我该如何解决这个问题 - 通过定义我的删除器,它不是模板,并将其定义写在 forward.cpp 中但是..我认为这应该有效,所以请帮助我找出为什么模板 + 虚拟使它成为不工作:(

最佳答案

这里发生了太多事情,所有这些都导致了这个错误......

首先,考虑一下:C++ 标准规定如果您这样做:

struct Incomplete;
void foo(Incomplete* p) { delete p; }

这是合法的,但如果 Incomplete 的完整定义原来有一个非平凡的析构函数,程序有未定义的行为。我认为这仅仅是为了与早期的类 C C++ 程序兼容。

所以,为了提高程序的安全性,unique_ptr的默认删除器使用“安全删除”,即无法针对不完整类型进行编译的删除。这意味着 unique_ptr 的实例化析构函数必须知道指向类的完整定义。

在您的程序中,任何使用 TestUniquePtr 的代码因此析构函数必须知道 Forward 的完整定义。 .

TestForward::foo使用析构函数。 make_ptr返回一个对象。 foo move-从此对象构造自己的返回值,然后销毁源。 (在实际生成的代码中,这很可能被返回值优化优化掉了,但没有它代码仍然有效。)

哪里/为什么是TestForward<3>::foo用过的?好吧,既然是虚的,那类的vtable在哪里实例化的地方就必须实例化。并且由于是模板实例化,所以在调用构造函数的任何地方都会实例化vtable(因为构造函数需要将vtable指针写入对象)。在 main 中调用了构造函数.

如果foo不是虚拟的,没有必要实例化它。如果TestForward不是模板,我猜你放了foo进入一些单独的源文件而不是标题,因此错误没有出现 main .


那么如何解决这个问题呢?

在典型的 Pimpl 上下文中,您可以通过严格控制谁实例化 unique_ptr 的析构函数来解决此问题.您显式声明接口(interface)类的析构函数并将定义放入已知 impl 类定义的源文件中。

但是,如果您想分发 unique_ptr s 到你不完整的类作为不透明句柄,你需要替换默认删除器。

// header.h
class Forward;
struct ForwardDeleter {
  void operator ()(Forward* ptr);
};
using TestUniquePtr = std::unique_ptr<Forward, ForwardDeleter>;

// forward.cpp
class Forward { ... };
void ForwardDeleter::operator ()(Forward* ptr) { delete ptr; }

关于c++ - std::unique_ptr 虚函数模板类中的不完整类型错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46484255/

相关文章:

stored-procedures - Subsonic 3.0.0.3 不为存储过程生成参数

c++ - 多次解包参数包

c++ - 如何将元组 <> 剥离回可变参数模板类型列表?

c++ - 是否有比 boost::spirit::hold_any 更快的替代方法并且 hold_any 会导致内存泄漏

c++ - 使用独立的 C++ 应用程序存储数据

c++ - 在 VC++ 中使用 GetOpenFileName() API 打开文件夹而不是文件

c++ - 在这个 C++ 代码中,std::string 可以被模板参数 T 替换吗?如果可以,Meyer 关于其性能成本的论点是否仍然适用?

templates - 使用可变参数模板建立索引

返回函数的 C++ 函数,xmemory 和 xrefwrap 中的错误 C2440、C2100

c++ - 错误 C4996 : 'std::_Copy_impl' : is it safe to disable it?