I'm writing a library. Its layout looks something akin to this:
/////////
// 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"
As you can see, the only type accessible from the outside is supposed to be A
, that's why B
is declared as an incomplete type in A.h
. The library itself compiles fine, but when it comes to using it in a program, the compiler issues errors like: invalid use of incomplete type B
when I try to create an object of type A
. These errors point to the vector
header, specifically, to the std::vector
's destructor, which apparently needs to know the size of the type it holds to deallocate the internal storage properly. What I think is happening is that the compiler is trying to instantiate std::vector<B>::~vector
in my program, which can't succeed for aforementioned reasons. However, there are symbols for std::vector<B>::~vector
in the library's binary, so I could do without instantiating it in the program. I'm looking for a way to tell that to the compiler. I've already tried changing MyLib.h
to something like this:
/////////////
// MyLib.h //
/////////////
#include "A.h"
extern template class std::vector<B>;
This unfortunately doesn't work, because extern
applies only to the code generation stage of the compilation, and the compiler still reports errors while parsing. I thought that maybe the compiler is trying to instantiate std::vector<B>::~vector
because A
has an implicit destructor, so I tried implementing a destructor manually like this:
/////////
// 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
...
This doesn't help either, the compiler is still trying to instantiate std::vector<B>::~vector
, even though it shouldn't ever be invoked outside the library code now. Is there a different way to achieve what I want or would it be best to choose a different method of information hiding for B
?
答案 0 :(得分:2)
不要试图明确地防止使用未定义类型的实例化,让编译器完成它的工作。如果您尝试手动阻止使用未定义类型的实例化,则可能存在违反ODR的风险,请在此处查看if-else depends on whether T is a complete type
您可以将值转储到unique_ptr
中的向量中以添加一个间接层。有关unique_ptr
如何使用不完整类型的详细信息,请在此处查看std::unique_ptr with an incomplete type won't compile例如
#include <iostream>
#include <vector>
#include <memory>
#include "something.hpp"
using std::cout;
using std::endl;
int main() {
Something something;
return 0;
}
#pragma once
#include <vector>
#include <memory>
class Incomplete;
class Something {
public:
Something();
~Something();
std::vector<std::unique_ptr<Incomplete>> incompletes;
};
#include "something.hpp"
class Incomplete {};
Something::Something() {}
Something::~Something() {}
另请考虑阅读此博文http://www.gotw.ca/gotw/028.htm,如果您反对,则概述动态分配的替代方案
答案 1 :(得分:2)
注意,可能的黑暗魔法: I am not sure whether(或从何时开始)允许 - 按标准 - 定义具有不完整类型的成员std::vector<T>
T
如果从当前翻译单元引用了向量成员函数 none (包括构造函数和析构函数)。我认为C ++ 17允许这样做。
从C ++ 11开始编译的一种可能性(虽然它可能是非法的,见上文)是使用union
成员来避免调用构造函数和析构函数std::vector
成员:
struct Hidden;
struct Public {
union Defer {
std::vector<Hidden> v;
Defer();
// add copy/move constructor if needed
~Defer();
} d;
};
现在,在您的实现文件中,您可以(并且必须)实际调用向量的构造函数和析构函数:
struct Hidden { /* whatever */ };
Public::Defer::Defer() { new (&v) std::vector<Hidden>(); }
Public::Defer::~Defer() { v.~vector<Hidden>(); }
当然,以任何方式使用成员d.v
都需要Hidden
的定义。因此,您应该将此类用途限制为inline
的{non Public
)成员函数,您可以在可以访问Hidden
的完整定义的文件中实现这些函数。
答案 2 :(得分:1)
你需要的是足够的向量空间,我所知道的每个实现中的所有向量都占用相同的空间,无论它们存储什么。
我们可以利用这一点,同时断言我们是对的。
struct A {
std::aligned_storage_t<sizeof(std::vector<int>), alignof(std::vector<int>)> v;
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>();
}
还手动写入复制/移动ctor / assign。
我们或许能够实现自动化,但这很棘手;我们希望事实上我们真的是B的向量,只能导致在A.cpp中创建代码,而不是其他地方。