为什么在使用 PIMPL 习语时这种类型不完整?

Why is this type incomplete when using the PIMPL idiom?

我使用的是 PIMPL 惯用语,特别是我使用的是 this post 提供的模板。给定下面的 类 集并使用 VS2015 Update 3 进行编译,我遇到了编译错误:

Error C2027 use of undefined type 'C::C_impl' (compiling source file src\A.cpp)

Error C2338 can't delete an incomplete type (compiling source file src\A.cpp)

Warning C4150 deletion of pointer to incomplete type 'C::C_impl'; no destructor called (compiling source file src\A.cpp)

我可以通过取消注释 C::~C() 来解决这个问题,这让我相信有些东西正在阻止 ~C() 自动生成,但我不明白是什么。根据 this reference,如果满足以下任一条件,则类型 T 的析构函数被隐式定义为已删除:

  1. T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
  2. T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
  3. T is a union and has a variant member with non-trivial destructor.
  4. The implicitly-declared destructor is virtual (because the base class has a virtual destructor) and the lookup for the deallocation function (operator delete() results in a call to ambiguous, deleted, or inaccessible function.

项目 #2、3 和 4 显然不适用于 C,我不认为 #1 适用,因为 pimpl<>C 的唯一成员) 显式定义析构函数。

有人可以解释一下这是怎么回事吗?

A.h

#pragma once
#include <Pimpl.h>

class A
{
private:
    struct A_impl;
    pimpl<A_impl> m_pimpl;
};

B.h

#pragma once
#include "C.h"

class B
{
private:
    C m_C;
};

C.h

#pragma once
#include <Pimpl.h>

class C
{
public:
    // Needed for the PIMPL pattern
    //~C();

private:
    struct C_impl;
    pimpl<C_impl> m_pimpl;
};

A.cpp

#include <memory>
#include "A.h"
#include "B.h"
#include <PimplImpl.h>

struct A::A_impl
{
    std::unique_ptr<B> m_pB;
};

// Ensure all the code for the template is compiled
template class pimpl<A::A_impl>;

C.cpp

#include <C.h>
#include <PimplImpl.h>

struct C::C_impl { };

// Needed for the PIMPL pattern
//C::~C() = default;

// Ensure all the code for the template is compiled
template class pimpl<C::C_impl>;

为了完整起见,上面引用的 post 中的 PIMPL 实现:

pimpl.h

#pragma once
#include <memory>

template<typename T>
class pimpl
{
private:
    std::unique_ptr<T> m;
public:
    pimpl();
    template<typename ...Args> pimpl(Args&& ...);
    ~pimpl();
    T* operator->();
    T& operator*();
};

PimplImpl.h

#pragma once
#include <utility>

template<typename T>
pimpl<T>::pimpl() : m{ new T{} } {}

template<typename T>
template<typename ...Args>
pimpl<T>::pimpl(Args&& ...args)
    : m{ new T{ std::forward<Args>(args)... } }
{
}

template<typename T>
pimpl<T>::~pimpl() {}

template<typename T>
T* pimpl<T>::operator->() { return m.get(); }

template<typename T>
T& pimpl<T>::operator*() { return *m.get(); }

关于上面代码的几点说明:

我认为问题是 std::unique_ptr 需要在实例化时知道销毁函数(即析构函数)。

使用默认析构函数 ~C(),编译器会在使用时生成它,这意味着它正在尝试使用 A.cpp 中可用的信息删除 pimpl<C_impl> 对象.当然,由于 C_impl 仅在此时声明,编译器不知道如何销毁 C_impl 对象,这就是您收到的错误。

取消注释 ~C(); 告诉编译器不要担心如何销毁将在别处定义的 C_impl。在您的情况下,在 C.cpp 中定义,其中 C_impl 的定义是已知的。

为了响应您的编辑,pimpl<C_impl> 的析构函数有 std::unique_ptr<C_impl>,它有一个非静态数据成员,析构函数不可访问(C_impl 在使用时是一个不完整的类型A.cpp).

您必须在定义 C::C_impl 之后手动定义 C 析构函数,因为默认情况下,编译器会尝试在使用时生成 C 析构函数,但它可能是找不到 C::C_impl 定义的地方(例如,它可以是 B.cpp)。