【问题标题】:Prevent instantiation of template class with an incomplete type防止使用不完整类型实例化模板类
【发布时间】:2017-11-21 19:02:13
【问题描述】:

我正在编写一个库。它的布局看起来类似于这样:

/////////
// 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"

如您所见,唯一可从外部访问的类型应该是A,这就是为什么BA.h 中被声明为不完整类型。该库本身编译得很好,但是在程序中使用它时,编译器会发出如下错误:invalid use of incomplete type B 当我尝试创建A 类型的对象时。这些错误指向vector 标头,特别是std::vector 的析构函数,它显然需要知道它持有的类型的大小才能正确释放内部存储。我认为正在发生的是编译器试图在我的程序中实例化std::vector&lt;B&gt;::~vector,由于上述原因而无法成功。但是,库的二进制文件中有std::vector&lt;B&gt;::~vector 的符号,所以我可以不用在程序中实例化它。我正在寻找一种方法来告诉编译器。我已经尝试将MyLib.h 更改为如下内容:

/////////////
// MyLib.h //
/////////////

#include "A.h"
extern template class std::vector<B>;

不幸的是这不起作用,因为extern仅适用于编译的代码生成阶段,编译器在解析时仍然会报告错误。我想也许编译器正在尝试实例化std::vector&lt;B&gt;::~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
...

这也无济于事,编译器仍在尝试实例化std::vector&lt;B&gt;::~vector,即使现在不应在库代码之外调用它。是否有不同的方法来实现我想要的,或者最好为B 选择不同的信息隐藏方法?

【问题讨论】:

  • 你不能使用不完整类型的std::vector,这是未定义的行为。
  • A 的每个构造函数(包括隐式复制/移动构造函数)都需要vector&lt;B&gt; 的析构函数。

标签: c++ templates instantiation incomplete-type


【解决方案1】:

不要试图显式地阻止未定义类型的实例化,让编译器完成它的工作。如果您尝试手动阻止未定义类型的实例化,则可能会违反 ODR,更多信息请查看此处if-else depends on whether T is a complete type

您可以将向量中的值转储到unique_ptrs 中以添加一个间接层。有关unique_ptr 如何处理不完整类型的更多信息,请查看此处std::unique_ptr with an incomplete type won't compile 例如

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

#pragma once

#include <vector>
#include <memory>

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

something.cpp

#include "something.hpp"

class Incomplete {};

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

还可以考虑阅读这篇博文http://www.gotw.ca/gotw/028.htm,如果您反对,它概述了动态分配的替代方案

【讨论】:

  • 我知道在这种情况下使用std::unique_ptr 之类的东西会起作用,只是在我看来这是一个非常麻烦的解决方案。它在没有充分理由的情况下引入了不必要的内存碎片。我想我宁愿公开B,也不愿改变我班级的布局。我当然想同时实现这两个目标,但我要说的是这个解决方案并没有真正对我说话。
  • @PiotrOlszewski 不幸的是,目前在 C++ 中没有您想要的解决方案,您需要以一种或另一种方式定义指针类型,您可以选择创建一个包含 Incomplete 的 POD 类型数据,然后在有可用定义时手动构造它,但这似乎也太麻烦了
【解决方案2】:

注意,可能的黑暗魔法: I am not sure whether(或从什么时候开始)允许 - 根据标准 - 定义成员 std::vector&lt;T&gt; 具有不完整类型 T 如果没有向量成员函数(包括构造函数和析构函数)从当前翻译单元引用。不过,我认为 C++17 允许这样做。

从 C++11 开始编译的一种可能性(尽管它可能是非法的,见上文)是使用 union 成员来避免调用构造函数以及 @ 的析构函数987654325@会员:

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 的定义。因此,您应该将此类用途限制为Public 的(非inline)成员函数,您在可以访问Hidden 完整定义的文件中实现这些函数。

【讨论】:

    【解决方案3】:

    您需要为向量提供足够的空间,并且我所知道的每个实现中的所有向量都占用相同的空间,无论它们存储什么。

    我们可以利用这一点,同时断言我们是对的。

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

    还要手动写copy/move ctor/assign。

    我们也许可以自动执行此操作,但这很棘手;我们希望我们实际上是 B 的向量这一事实仅导致在 A.cpp 中创建代码,别无他处。

    【讨论】:

      猜你喜欢
      • 2016-05-20
      • 2021-10-22
      • 2022-11-13
      • 2019-10-20
      • 1970-01-01
      • 2021-12-11
      • 1970-01-01
      相关资源
      最近更新 更多