为什么加一个空析构函数可以解决“error: invalid application of 'sizeof' to an incomplete type using unique_ptr”问题?

36

我正在将类STFT实现为Pimpl模式。在头文件中加入以下代码后可以通过编译:

class STFT; // pimpl off to prevent point name clash

class Whatever
{
private:
    STFT* stft;

并且在实现中:

#include "STFT.h"
Whatever::Whatever() : stft(new STFT()) {
// blah blah
}

Whatever::~Whatever() {
    delete stft; // pure evil
}

然而,将头文件中的原始指针切换为std::unique_ptr<STFT> stft;并删除析构函数后,我得到以下错误:

error: invalid application of 'sizeof' to an incomplete type 'STFT' static_assert(sizeof(_Tp) > 0, "default_delete can not delete incomplete type");

但是,如果我只提供一个空析构函数Whatever::~Whatever(){},那么它就可以编译通过。这让我感到非常困惑。请告诉我这个没有意义的析构函数对我的作用是什么。


刚刚测试过了,没有出现错误 :/ - learnvst
2个回答

47
如果我们查看 std::unique_ptr 的 cppreference 文档:

std::unique_ptr 可以用于不完整类型 T,例如在 Pimpl 惯用法中作为句柄使用。 如果使用默认删除器,则必须在代码中调用删除器的位置(即在析构函数、移动赋值运算符和重置成员函数中)T 必须完整。 (相反地,std::shared_ptr 不能从指向不完整类型的原始指针构造,但可以在 T 不完整的情况下销毁。)

我们可以在下面的代码中看到:
#include <memory>

class STFT; // pimpl off to prevent point name clash

class Whatever
{
    public:
     ~Whatever() ;
    private:
      std::unique_ptr<STFT> stft;
} ;

//class STFT{};

Whatever::~Whatever() {}

int main(){}

在定义Whatever的析构函数之前注释STFT的定义时,无法满足要求,因为这需要stft的析构函数,而stft的析构函数又需要完整的STFT
因此,在您的实现文件中,当定义Whatever::~Whatever()时,STFT已经完整,否则将创建默认的STFT,但不完整。

30

我通常为提供这样的析构函数使用以下成语(在实现文件中):

#include "STFT.h"

Whatever::~Whatever() = default;

重要的是指向的类型需要在某个完整的位置。


4
使用那个语法会出现相同的错误。我需要明确声明一个空的析构函数。 - learnvst
8
这取决于你放在哪里。如果你把它放在头文件中,其中STFT是不完整的,那么它就像让编译器在需要时生成一样糟糕。它需要在实现文件中,在#include <stft>之后,所有指向的类型都是完整的。如果它不起作用,我自己也不会使用它。 - Toby Speight
啊哈。好的。那就行了。 - learnvst
1
Whatever::~Whatever() {}相比,有任何区别吗?那个少了7个字符 :-P - Nikos C.
4
从技术上讲,显式默认析构函数和用户提供的空析构函数之间存在差异,但我现在想不到实际上有什么区别。我发现显式的default更容易阅读,代价就是代价! - Toby Speight
2
这是实际的答案。Dtor 应该在编译单元中,而不是在头文件中。我浪费了半天时间来找出为什么它无法编译。 - Anton K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接