【问题标题】:Unique_ptr usage for pimpl - doesn't compile even though destructor is declaredpimpl 的 Unique_ptr 用法 - 即使声明了析构函数也不会编译
【发布时间】:2021-10-21 21:30:15
【问题描述】:

我正在尝试将 unique_ptr 用于 pimpl 成语。所以我在类中声明了一个析构函数,这样在没有定义impl类的地方就不会实例化unique_ptr删除,然后我在另一个文件中定义它。

这是我的布局:

包装器.h:

#pragma once
#include <memory>

struct Wrapper
{
    class Impl;
    ~Wrapper();
    std::unique_ptr<Impl> _impl;
};

包装器.cpp:

#include "wrapper.h"

class Wrapper::Impl {};
Wrapper::~Wrapper() = default;

这个文件编译得很好。 但是,在编译 main.cpp 时出现不完整的类型错误(请参阅下面的错误):

main.cpp:

#include "wrapper.h"

int main()
{
    Wrapper w;
    return 0;
}

但是,如果我将 wrapper.cpp 中的两行添加到 main.cpp 的末尾,它编译得很好。 我不明白两件事:

  1. 首先为什么会发生错误?我认为在类中声明析构函数会移动删除调用点?
  2. 为什么在 main.cpp 文件的末尾添加代码会影响此错误?我虽然“如果使用默认删除器,T 必须是完整的

我错过了什么?

更新:

按照@AdrianMole 的建议,我在类 Wrapper 定义中添加了一个 ctor 声明,并且由于某种原因它修复了错误,即使错误(和 unique_ptr 规范)引用了析构函数。

更新了 wrapper.h:

struct Wrapper
{
    class Impl;
    Wrapper();
    ~Wrapper();
    std::unique_ptr<Impl> _impl;
};

所以我要添加一个问题:

  1. 为什么添加构造函数声明可以解决这个问题?

这些是我在使用 MSVC 时遇到的错误,但在 clang 或 gcc 中也会出现类似的错误(我尝试过在线编译器):

memory(2536,1): error C2027: use of undefined type 'Wrapper::Impl'

wrapper.h(7): message : 查看 'Wrapper::Impl' 的声明

memory(2535): message : 在编译类模板成员函数'void std::default_deleteWrapper::Impl::operator ()(_Ty *) noexcept const'时

    with

    [

        _Ty=Wrapper::Impl

    ]

memory(2647): message : 请参阅正在编译的函数模板实例化 'void std::default_deleteWrapper::Impl::operator ()(_Ty *) noexcept const' 的引用

    with

    [

        _Ty=Wrapper::Impl

    ]

memory(2574): message : 请参阅正在编译的类模板实例化 'std::default_deleteWrapper::Impl' 的引用

wrapper.h(9): message : 请参阅对类模板实例化 'std::unique_ptrWrapper::Impl,std::default_delete<:impl>' 的引用

memory(2536,25):错误 C2338:无法删除不完整的类型

memory(2537,1):警告 C4150:删除指向不完整类型“Wrapper::Impl”的指针;没有调用析构函数

wrapper.h(7): message : 查看 'Wrapper::Impl' 的声明

【问题讨论】:

  • 你不应该在wrapper.cpp 中也有Wrapper::Wrapper() : _impl(new Impl) {} 吗?
  • @m88 编译不需要,所以省略了构造代码。无论如何,在现实生活中的用例中,它不是在构造函数中完成,而是在接收到远程参数之后完成。
  • 问题不在于你定义Wrapper析构函数的位置,而是你定义Impl类的位置。我猜std::unique_ptr&lt;Impl&gt; 对象的默认构造函数需要知道Impl 的完整定义。
  • @AdrianMole 谢谢,但实际上 unique_ptr 不需要在默认构造函数上完全实现该类型(问题中的 cppreference 链接中的更多详细信息)。并且它没有解释为什么在代码修复它之后添加定义
  • 很好奇。 clang-cl 编译器给出:error : invalid application of 'sizeof' to an不完整类型'Wrapper::Impl' ... 在成员函数'std::unique_ptr::~unique_ptr' 在这里请求 (所以我猜这是unique_ptr 对象的默认析构函数。)但是是的 - 不知道为什么在代码解决了这个问题。

标签: c++ compiler-errors pimpl-idiom


【解决方案1】:

任何构造函数定义(包括隐式定义的默认构造函数)都可能调用类类型的所有成员对象的类型的析构函数。这样做的基本原理是,如果构造稍后的成员抛出,则需要调用所有先前成员的析构函数。在最后一个成员的情况下,这不是严格要求的,但标准没有做这个例外。

例如,如果您的班级是:

struct complete_type_with_throwing_constructor {
    complete_type_with_throwing_constructor() { throw 0; }
};

struct Wrapper
{
    class Impl;
    ~Wrapper();
    std::unique_ptr<Impl> _impl;
    complete_type_with_throwing_constructor x;
};

那么Wrapper的隐式默认构造函数会构造_impl,然后在x的默认构造函数抛出后销毁它。

如果您显式声明默认构造函数,这就是您的代码可以正常工作的原因。这会将定义移动到可以实例化 std::unique_ptr&lt;Impl&gt; 的析构函数的位置。


至于您的第二个问题,您是对的,之后添加定义时代码仍然无法工作。只是编译器没有检测到,所以没有报错。

【讨论】:

  • 谢谢!我没有考虑激活成员析构函数的构造函数 - 这是一个好点。它实际上解决了我的问题,即使它没有回答关于之后添加代码的部分,所以我会相应地修改问题。
猜你喜欢
  • 1970-01-01
  • 2014-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-25
  • 1970-01-01
  • 2021-09-20
相关资源
最近更新 更多