类C
的方法之一需要返回一个包含一对整数和C
的新实例的struct
。它可能看起来很尴尬,但考虑到整体设计,这很有意义(想想一个Waveform
类将自身的一个范围作为副本返回,并指示范围的开始和结束位置)。
问题是这似乎是不允许的。我可以重新设计我的类以规避这个问题,但你能解释一下为什么从编译器的角度来看,这不能做到
吗struct S {
struct S2 {
S s;
};
};
因为S
是一个不完整的类型(这是编译器错误),相反,这很好
struct C {
struct C1 {
C makeC() { return C(); }
};
};
实质性差异在哪里?
在你尝试定义S::S2
时,类型 S
仍然是一个不完整的类型,因为它的定义尚未完成。并且类数据成员必须具有完整的类型。
您可以像这样轻松修复它:
struct S
{
struct S2; // declare only, don't define
// ...
};
struct S::S2
{
S s; // now "S" is a complete type
};
它本质上是一个C++的设计决策,在定义结束之前不考虑一个类型是完整的。这可以防止许多病理情况,如以下示例所示:
struct X
{
struct Y { X a; };
int b[sizeof(Y)]; // "sizeof" requires a complete type
};
当你想要定义一个类时,你需要一个所有嵌入(非引用和非指针)成员的完整定义。定义类时,未完全定义类。也就是说,在类定义中,定义的类只是声明的,而不是定义的。
也就是说,您仍然可以在嵌套类中拥有类的成员。你只需要在定义外部类之后定义嵌套类:
struct S {
struct S2;
};
struct S::S2 {
S s;
};
由于S
完全由第一个右大括号定义,因此在此点之后可以将其用作嵌入构件。
中定义成员函数时,定义将被解析为紧跟在最接近的命名空间级别的类定义之后。也就是说,成员函数定义知道完全定义的类。这同样不适用于嵌套类。
为什么,但我可以推测这个反例:
struct S {
struct S2 {
S s;
};
S2 s2;
};
在这种情况下,尝试在扫描S
并确定其大小之前S2
将失败。我怀疑递归可能会给编译器带来困难,而这种困难通常是标准中规则的原因。
也就是说,我认为您的解决方案不太适合您所描述的问题。我会在S
上制作S2
模板,无论如何都不会嵌套。然后,我将能够对模板参数使用引用S&
,而不是S
本身。因此,范围将由两个整数和对原始数组对象的引用表示。从此范围制作新副本将是一个单独的操作。
编译器无法计算封闭类的大小。
不过你可以写S* s;
。
这个答案侧重于你的两个例子之间的区别,因为其他答案已经解释了为什么第一个不起作用。
简而言之,您的第一个示例以需要S
才能完成的方式使用S
中的S
。
第二个示例要求C
在编译函数体后立即完成。您的代码等效于
struct C {
struct C1 {
C makeC();
};
};
inline C C::C1::makeC() {
return C();
}
这意味着,编译器会自动"推迟"函数体。对于函数声明,编译器只需要知道C
是一种类型,但如果C
不完整,它就可以工作,直到那时。编译函数定义时,类型C
现已完成。