相互依赖的C ++类,由std :: vector持有

时间:2016-02-17 15:54:40

标签: c++ vector

我很好奇是否有可能创建两个类,每个类都拥有另一个类的std::vector。我的第一个猜测是,这是不可能的,因为std::vector需要一个完整的类型,而不仅仅是一个前向声明。

#include <vector>

class B;
class A { std::vector<B> b; };
class B { std::vector<A> a; };

我认为std::vector<B>的声明会立即导致失败,因为此时B的类型不完整。但是,这在gcc和clang下成功编译,没有任何警告。为什么这不会导致错误?

3 个答案:

答案 0 :(得分:5)

T.C评论说,这实际上是未定义的行为,正在对标准的变更请求中解决。违反的规则显然是[res.on.functions] 2.5:

  

特别是,在以下情况下效果未定义:[...]

     
      
  • 如果在实例化模板组件时将不完整类型(3.9)用作模板参数,除非特别允许该组件。
  •   

无论如何它的作用(以及它可以标准化的原因)是双重的:

  1. 您的程序只包含类型定义;没有创建对象,没有调用函数,也没有生成代码。如果我们通过消除B的定义(不需要它)来简化它,然后尝试创建A的实例,它就会失败:

    class B;
    class A { std::vector<B> b; };
    A a; // error: ctor and dtor undefined for incomplete type B.
    

    正如预期的那样,失败也是一个简单的

    std::vector<B> b;
    

    两种情况下的原因是编译器必须生成代码,而不仅仅是语法相关的纯类型声明。

    在其他环境中使用vector type 也是可以的:

    typedef std::vector<B> BVec;
    
  2. 可以定义班级A,因为正如尼古拉在答案中正确说明的那样,std::vector<B>的大小,以及A成员的大小b,不依赖于B的定义(因为向量包含指向元素数组的指针,而不是适当的数组)。

答案 1 :(得分:4)

这会编译并运行正常,因为std::vector使用指针而不是AB的确切定义。您可以修改您的示例以在类的定义中使用单个实例或数组,如此

class B;
class A { public: B b[2]; };
class B { public: A a[2]; };

这显然无法编译,因为您尝试使用类的定义。编译器的错误就像你期待的那样

  

错误:字段'b'的类型不完整'B [2]'

然而

class B;
class A { public: B* b; };
class B { public: A* a; };

就像std::vector一样。因此,您不需要完全定义的类来使用指针或对此类型的引用。

还有模板的简化示例

template<typename T>
struct C {
    T* t;
};

class B;
class A { public: C<B> b; };
class B { public: C<A> a; };

这也可以,当然你可以实例化它

int main() {
    B b;
    A a;
}

答案 2 :(得分:3)

这是因为模板实例化规则。在A声明时,只需要std::vector<B>接口进行实例化。

由于std::vector的接口仅使用指针和对其值类型的引用,并且示例中的特化实例化但未使用其任何函数,因此不会出现编译器错误。< / p>

至于为什么编译器没有生成A::A() - 这会通过调用std::vector<B>的构造函数来触发错误 - 因为你的上下文只需要它的声明。如果编译器看到它们被使用,则只需要为这些特殊成员函数生成默认值。

参考

  

§14.7.1

     

[...]类模板特化的隐式实例化导致类成员函数,成员类,作用域成员枚举,静态数据成员和类成员函数,成员类,定义成员枚举的定义或默认参数的隐式实例化。成员模板[。]

     

§12

     

[1] [...]当程序没有显式声明它们时,实现将隐式声明某些类类型的这些[特殊]成员函数。 如果使用它们,实现将隐式定义它们。