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>();
}