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

时间:2016-07-21 14:14:00

标签: c++ pimpl-idiom incomplete-type gotw

我使用PIMPL习语,特别是我使用this post提供的模板。鉴于下面的一组类并使用VS2015 Update 3进行编译,我收到了编译错误:

  

错误C2027使用未定义类型' C :: C_impl' (编译源文件src \ A.cpp)

     

错误C2338无法删除不完整的类型(编译源文件src \ A.cpp)

     

警告C4150删除指向不完整类型的指针' C :: C_impl&#39 ;;没有析构函数调用(编译源文件src \ A.cpp)

我可以通过取消注释C::~C()来解决这个问题,这会让我相信某些事情会阻止~C()自动生成但我不明白什么。根据{{​​3}},如果满足以下任何条件,则类型T的析构函数被隐式定义为已删除:

  
      
  1. T有一个非静态数据成员,无法被破坏(已删除或无法访问析构函数)
  2.   
  3. T具有无法销毁的直接或虚拟基类(已删除或无法访问的析构函数)
  4.   
  5. T是一个联合体,并且具有带有非平凡析构函数的变体成员。
  6.   
  7. 隐式声明的析构函数是虚拟的(因为基类具有虚拟析构函数),并且查找解除分配函数(operator delete()会导致调用不明确,已删除或无法访问的函数。
  8.   

项目#2,3和4显然不适用于C,我不相信#1适用,因为pimpl<>C&#39; s only member)明确定义析构函数。

有人可以解释一下发生了什么吗?

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>;

为完整起见,上面引用的帖子中的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(); }

关于上述代码的一些注释:

  • 我试图向我的图书馆的消费者展示AC,并将B保留在内部。
  • 这里没有B.cpp,这将是empy。

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:1)

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

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

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

为了响应您的编辑,pimpl<C_impl>的析构函数具有std::unique_ptr<C_impl>,其具有非静态数据成员且具有无法访问的析构函数(C_impl在使用时是一个不完整的类型{ {1}})。