C++ 防止实例化类型不完整的模板类
我在写图书馆。其布局与此类似:C++ 防止实例化类型不完整的模板类,c++,templates,instantiation,incomplete-type,C++,Templates,Instantiation,Incomplete Type,我在写图书馆。其布局与此类似: ///////// // A.h // ///////// #include <vector> class B; class A { std::vector<B> Bs; public: ... }; ///////// // B.h // ///////// class B { ... } /////////// // A.cpp // /////////// #include "A.h" #inc
/////////
// A.h //
/////////
#include <vector>
class B;
class A
{
std::vector<B> Bs;
public:
...
};
/////////
// B.h //
/////////
class B
{
...
}
///////////
// A.cpp //
///////////
#include "A.h"
#include "B.h"
// Implementation of A follows
...
///////////
// B.cpp //
///////////
#include "B.h"
// Implementation of B follows
...
/////////////
// MyLib.h //
/////////////
#include "A.h"
不幸的是,这不起作用,因为extern
仅适用于编译的代码生成阶段,并且编译器在解析时仍然报告错误。我认为可能编译器正试图实例化std::vector::~vector
,因为A
有一个隐式析构函数,所以我尝试像这样手动实现析构函数:
/////////////
// MyLib.h //
/////////////
#include "A.h"
extern template class std::vector<B>;
/////////
// A.h //
/////////
#include <vector>
class B;
class A
{
std::vector<B> Bs;
~A();
public:
...
};
///////////
// A.cpp //
///////////
#include "A.h"
#include "B.h"
A::~A() {}
// Further implementation of A follows
...
/////////
//A.h//
/////////
#包括
乙级;;
甲级
{
std::向量Bs;
~A();
公众:
...
};
///////////
//A.cpp//
///////////
#包括“A.h”
#包括“B.h”
A::~A(){}
//进一步执行一项决议如下:
...
这也无济于事,编译器仍在尝试实例化
std::vector::~vector
,即使现在不应该在库代码之外调用它。是否有不同的方法来实现我想要的,或者最好为B
选择不同的信息隐藏方法 不要尝试显式阻止未定义类型的实例化,让编译器完成它的工作。如果您试图手动阻止未定义类型的实例化,则可能会违反ODR,有关详细信息,请参见此处
您可以在unique_ptr
s中转储向量中的值,以添加一层间接寻址。有关unique\u ptr
如何处理不完整类型的更多信息,请参见此处示例
main.cpp
也考虑阅读这篇博文,它概述了如果你反对
< P> <强>注意,可能的暗魔法:< /强>(或从何时开始)的动态分配的替代方案。根据标准,如果当前翻译单元中未引用向量成员函数(包括构造函数和析构函数),则允许定义类型不完整的成员std::vector
。不过,我认为C++17允许这样做
自C++11以来编译的一种可能性(尽管可能是非法的,见上文)是使用联合
成员来避免调用std::vector
成员的构造函数和析构函数:
struct Hidden;
struct Public {
union Defer {
std::vector<Hidden> v;
Defer();
// add copy/move constructor if needed
~Defer();
} d;
};
struct隐藏;
结构公共{
工会延期{
std::向量v;
延迟();
//如果需要,添加复制/移动构造函数
~Defer();
}d;
};
现在,在您的实现文件中,您可以(并且必须)实际调用向量的构造函数和析构函数:
struct Hidden { /* whatever */ };
Public::Defer::Defer() { new (&v) std::vector<Hidden>(); }
Public::Defer::~Defer() { v.~vector<Hidden>(); }
struct Hidden{/*whatever*/};
Public::Defer::Defer(){new(&v)std::vector();}
Public::Defer::~Defer(){v.~vector();}
当然,以任何方式使用成员
d.v
都需要定义隐藏的
。因此,您应该将此类使用限制在公共
的(非内联
)成员函数中,您可以在文件中实现这些函数,这些函数可以访问隐藏
的完整定义,您需要的是向量的足够空间,我知道的每个实现中的所有向量都占用相同的空间,不管它们存储什么
我们可以利用这一点,同时断言我们是正确的
struct A {
std::aligned_storage_t<sizeof(std::vector<int>), alignof(std::vector<int>)> v;
A();
~A();
};
结构A{
标准::对齐存储电视;
A();
~A();
};
在A.cpp中
#include<b.h>
static_assert(sizeof(std::vector<B>)==sizeof(std::vector<int>), "size mismatch");
static_assert(alignof(std::vector<B>)==alignof(std::vector<int>), "align mismatch");
std::vector<B>& get_v(A& a){ return *(std::vector<B>*)&a.v; }
std::vector<B> const& get_v(A const& a){ return *(std::vector<B> const*)&a.v; }
A::A(){
::new ((void*)&v) std::vector<B>();
try{
// rest of ctor
}catch(...){
get_v(*this).~std::vector<B>();
throw;
}
}
A::~A(){
get_v(*this).~std::vector<B>();
}
#包括
静态断言(sizeof(std::vector)=sizeof(std::vector),“大小不匹配”);
静态断言(alignof(std::vector)=alignof(std::vector),“对齐不匹配”);
std::vector&get_v(A&A){return*(std::vector*)&A.v;}
std::vector const&get_v(A const&A){return*(std::vector const*)&A.v;}
A::A(){
::新((void*)&v)标准::向量();
试一试{
//其余部分
}捕获(…){
获取v(*此)。~std::vector();
投掷;
}
}
A::~A(){
获取v(*此)。~std::vector();
}
也可以手动写入复制/移动/分配
我们也许能够自动完成这项工作,但这很棘手;我们希望事实上我们是B的向量,只导致代码在a.cpp中创建,而不是在其他地方。您不能将
std::vector
用于不完整的类型,这是未定义的行为。a的每个构造函数(包括隐式复制/移动构造函数)需要向量的析构函数。我知道在这种情况下使用类似于std::unique_ptr的东西是可行的,只是在我看来这是一个非常麻烦的解决方案。它引入了不必要的内存碎片,而实际上并没有什么好的理由。我想我宁愿公开B
,也不愿改变我的课堂布局。当然,我想实现这两个目标,但我的意思是,这个解决方案并没有真正地告诉我。“PiotrOlszewski,目前C++中没有你想要的解决方案,不幸的是,你需要用一种或那种方式定义指针类型。您可以选择创建一个包含不完整的数据的POD类型,然后在定义可用时手动构建它,但这似乎太麻烦了
struct Hidden { /* whatever */ };
Public::Defer::Defer() { new (&v) std::vector<Hidden>(); }
Public::Defer::~Defer() { v.~vector<Hidden>(); }
struct A {
std::aligned_storage_t<sizeof(std::vector<int>), alignof(std::vector<int>)> v;
A();
~A();
};
#include<b.h>
static_assert(sizeof(std::vector<B>)==sizeof(std::vector<int>), "size mismatch");
static_assert(alignof(std::vector<B>)==alignof(std::vector<int>), "align mismatch");
std::vector<B>& get_v(A& a){ return *(std::vector<B>*)&a.v; }
std::vector<B> const& get_v(A const& a){ return *(std::vector<B> const*)&a.v; }
A::A(){
::new ((void*)&v) std::vector<B>();
try{
// rest of ctor
}catch(...){
get_v(*this).~std::vector<B>();
throw;
}
}
A::~A(){
get_v(*this).~std::vector<B>();
}