关于c ++:嵌套类声明:模板与非模板外部类

Nested class declaration: template vs non-template outer class

我有一个C++模板类,里面有一个嵌套类,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<int d>
class Outer_t
{
public:
    class Inner;

    Inner i;
};

template<int d>
class Outer_t<d>::Inner
{
public:
    float x;
};

int main ()
{
    Outer_t<3> o_t; // 3 or any arbitrary int
    o_t.i.x = 1.0;

  return 0;
}

这编译没有任何问题。但是,一旦我声明了类似的非模板类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Outer_1
{
public:
    class Inner;

    Inner i;
};

class Outer_1::Inner
{
public:
    float x;
};

int main ()
{
    Outer_1 o1;
    o1.i.x = 1.0;

  return 0;
}

我开始得到以下错误(我使用的是GCC4.6.3):"错误:字段‘i’的类型不完整"。我知道,我可以通过在外部类内部直接定义内部类来解决这个问题:

1
2
3
4
5
6
7
8
9
10
class Outer_2
{
public:
    class Inner {
    public:
        float x;
    };

    Inner i;
};

这将编译,但我希望避免在内联中定义嵌套类。所以我有两个问题:模板和非模板嵌套类声明之间这种明显奇怪的差异的原因是什么?有没有一种优雅的方法来声明和使用外部类中的Nester类,同时避免以与模板类相同的风格内联定义它?提前感谢您的帮助!


为什么会出现编译错误?

编译器需要知道成员变量类型的大小才能进行对象布局。对于Outer_1,由于Inner只是一个转发声明,我们不知道它的大小。因此出现编译错误。然而,对于Outer_2来说,定义是内联的,因此我们在得到Inner类型的成员变量时就知道它的大小。

为什么模板可以通过?

另一方面,类模板在模板实例化发生之前不会被定义。在您的示例中,隐式实例化发生在main中。此时,Inner的定义是完整的,因此它的大小是已知的。我们可以看到,在Inner的定义之前使用显式实例化就是这种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 template<int d>
 class Outer_t
 {
 public:
     class Inner;

     Inner i;
 };

 template class Outer_t<3>;  // explicit instantation

 template<int d>
 class Outer_t<d>::Inner
 {
 public:
     float x;
 };

 int main ()
 {
     Outer_t<3> o_t; // 3 or any arbitrary int
     o_t.i.x = 1.0;

   return 0;
 }

Clang产生以下错误:

1
2
3
4
5
6
7
8
9
 a.cc:7:11: error: implicit instantiation of undefined member 'Outer_t<3>::Inner'
     Inner i;
           ^
 a.cc:10:16: note: in instantiation of template class 'Outer_t<3>' requested here
 template class Outer_t<3>;
           ^
 a.cc:5:11: note: member is declared here
     class Inner;
           ^

解决方案

如果您想提取嵌套类的定义,我建议的解决方案是像Outer_t那样对其进行模板化,但为方便起见提供一个别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 template <typename Dummy = void>
 class Outer_1_Impl {
   public:

   class Inner;

   Inner i;
 };

 template <typename Dummy>
 class Outer_1_Impl<Dummy>::Inner {
   public:
   float x;
 };

 using Outer_1 = Outer_1_Impl<>;

 int main () {
   Outer_1 o1;
   o1.i.x = 1.0;
 }


在定义类时,对象中只能有具有完整定义的非静态成员。在您的非模板示例中,Outer_1::Inner显然不完整,因为它只是声明到目前为止。创建EDOCX1[1]成员的唯一方法实际上是像对EDOCX1[2]那样定义它。

现在,为什么在使用类模板时不出现这个问题?答案是:当您在main()中实例化Outer_t时,实际上有一个完整的Outer_t::Inner可访问的定义!…在用某种类型的T实例化Outer_t之前,并不真正需要Outer_t的定义。