为什么在使用 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 的析构函数被隐式定义为已删除:
- T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
- T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
- T is a union and has a variant member with non-trivial destructor.
- 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(); }
关于上面代码的几点说明:
- 我试图向我的图书馆的消费者公开
A
和 C
并保持 B
内部。
- 这里没有B.cpp,就是空的
我认为问题是 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)。
我使用的是 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 的析构函数被隐式定义为已删除:
- T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
- T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
- T is a union and has a variant member with non-trivial destructor.
- 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(); }
关于上面代码的几点说明:
- 我试图向我的图书馆的消费者公开
A
和C
并保持B
内部。 - 这里没有B.cpp,就是空的
我认为问题是 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)。