简体   繁体   English

防止使用不完整类型实例化模板类

[英]Prevent instantiation of template class with an incomplete type

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 Ah . 正如你所看到的,唯一可以从外部访问的类型应该是A ,这就是为什么BAh被声明为不完整类型的原因。 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 . 库本身编译得很好,但是当在程序中使用它时,编译器会发出以下错误:当我尝试创建类型A的对象时, invalid use of incomplete type B 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. 这些错误指向了vector头,特别是指向std::vector的析构函数,它显然需要知道它所拥有的类型的大小才能正确释放内部存储。 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. 我认为正在发生的是编译器试图在我的程序中实例化std::vector<B>::~vector 〜vector,由于上述原因这不能成功。 However, there are symbols for std::vector<B>::~vector in the library's binary, so I could do without instantiating it in the program. 但是,在库的二进制文件中有std::vector<B>::~vector符号,所以我可以不在程序中实例化它。 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更改为以下内容:

/////////////
// 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. 遗憾的是,这不起作用,因为extern仅适用于编译的代码生成阶段,编译器在解析时仍会报告错误。 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: 我想也许编译器试图实例化std::vector<B>::~vector因为A有一个隐式析构函数,所以我尝试像这样手动实现析构函数:

/////////
// 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. 这也无济于事,编译器仍在尝试实例化std::vector<B>::~vector ,即使它现在不应该在库代码之外调用。 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 ? 是否有不同的方式来实现我想要的或者这将是最好的选择的信息隐藏了不同的方法B

Don't try and prevent instantiations with undefined types explicitly, let the compiler do its job. 不要试图明确地防止使用未定义类型的实例化,让编译器完成它的工作。 If you try and prevent instantiations with undefined types manually you might risk violating ODR, for more look here if-else depends on whether T is a complete type 如果您尝试并手动阻止使用未定义类型的实例化,则可能存在违反ODR的风险, 如果 -更多请查看- 否则取决于T是否为完整类型

You can dump the values in the vector in unique_ptr s to add a layer of indirection. 您可以将值转储到unique_ptr中的向量中以添加间接层。 For more information about how unique_ptr works with incomplete types look here std::unique_ptr with an incomplete type won't compile For example 有关unique_ptr如何使用不完整类型的更多信息,请在此处查看具有不完整类型的std :: unique_ptr将无法编译例如

main.cpp main.cpp中

#include <iostream>
#include <vector>
#include <memory>

#include "something.hpp"

using std::cout;
using std::endl;

int main() {

    Something something;

    return 0;
}

something.hpp something.hpp

#pragma once

#include <vector>
#include <memory>

class Incomplete;
class Something {
public:
    Something();
    ~Something();
    std::vector<std::unique_ptr<Incomplete>> incompletes;
};

something.cpp something.cpp

#include "something.hpp"

class Incomplete {};

Something::Something() {}
Something::~Something() {}

Also consider reading this blog post http://www.gotw.ca/gotw/028.htm , it outlines an alternative to dynamic allocation if you are against that 另请考虑阅读这篇博客文章http://www.gotw.ca/gotw/028.htm ,它概述了动态分配的替代方案,如果你反对的话

ATTENTION, POSSIBLE DARK MAGIC: I am not sure whether (or since when) one is allowed - by the standard - to define a member std::vector<T> with an incomplete type T if none of the vectors member functions ( including constructors and destructor ) is referenced from the current translation unit. 注意,可能的黑暗魔法: 我不确定是否 (或从何时)允许 - 通过标准 - 定义具有不完整类型T的成员std::vector<T>如果没有向量成员函数( 包括构造函数)和析构函数 )从当前翻译单元引用。 I think C++17 allows this, though. 我认为C ++ 17允许这样做。

One possibility that compiles since C++11 (though it could be illegal, see above) is the use of an union member to avoid calls to both the constructor(s) as well as to the destructor of the std::vector member: 从C ++ 11开始编译的一种可能性(虽然它可能是非法的,见上文)是使用union成员来避免调用构造函数以及std::vector成员的析构函数:

struct Hidden;

struct Public {
    union Defer {
        std::vector<Hidden> v;
        Defer();
        // add copy/move constructor if needed
        ~Defer();
    } d;
};

Now, in your implementation file, you can (and must) actually call the constructor(s) and destructor of the vector: 现在,在您的实现文件中,您可以(并且必须)实际调用向量的构造函数和析构函数:

struct Hidden { /* whatever */ };
Public::Defer::Defer() { new (&v) std::vector<Hidden>(); }
Public::Defer::~Defer() { v.~vector<Hidden>(); }

Of course, using the member dv in any way will need the definition of Hidden . 当然, 以任何方式使用成员dv都需要Hidden的定义 Thus you should limit such uses to the (non inline ) member functions of Public , which you implement in file(s) that have access to the complete definition of Hidden . 因此,您应该将此类用途限制为Public的(非inline )成员函数,您可以在可以访问Hidden完整定义的文件中实现这些函数。

What you need is enough space for the vector, and all vectors in every implementation I know of take the same space regardless of what they store. 你需要的是足够的向量空间,我所知道的每个实现中的所有向量都占用相同的空间,无论它们存储什么。

We can exploit this, while asserting we are right. 我们可以利用这一点,同时断言我们是正确的。

struct A {
  std::aligned_storage_t<sizeof(std::vector<int>), alignof(std::vector<int>)> v;
  A();
  ~A();
};

in A.cpp 在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>();
}

Also manually write copy/move ctor/assign. 也可以手动写入复制/移动ctor / assign。

We might be able to automate this, but it is tricky; 我们或许能够实现自动化,但这很棘手; we want the fact we are really a vector of B to only cause code to be created in A.cpp, nowhere else. 我们希望我们真的是B的向量,只能导致在A.cpp中创建代码,而不是其他地方。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM