类C
的一种方法需要返回包含一对整数和struct
的新实例的C
。它可能看起来很尴尬,但考虑到整体设计,这很有意义(想想一个Waveform
类将一系列自身作为副本返回,并指示范围的开始和结束位置。)
问题是似乎不允许这样做。我可以重新设计我的课程以避免这个问题,但是你可以解释一下为什么,从编译器的角度来看,这是不可能做到的
struct S {
struct S2 {
S s;
};
};
因为S
是一个不完整的类型(这是编译器错误),相反,这是完全正常的
struct C {
struct C1 {
C makeC() { return C(); }
};
};
哪里有实质性差异?
答案 0 :(得分:5)
在您尝试定义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
};
答案 1 :(得分:2)
如果要定义类,则需要完整定义所有嵌入(非引用和非指针)成员。在定义类时,类没有完全定义。也就是说,在类定义中,定义下的类只是声明,而不是定义。
也就是说,您仍然可以在嵌套类中拥有类的成员。您只需在定义外部类后定义嵌套类:
struct S {
struct S2;
};
struct S::S2 {
S s;
};
由于S
完全由第一个右大括号定义,因此在此之后它可以用作嵌入式成员。
在类定义中定义成员函数时,定义将被解析,就好像它出现在最接近的命名空间级别的类定义之后。也就是说,成员函数定义知道完全定义的类。这同样适用于嵌套类。
答案 2 :(得分:1)
我不确切知道为什么,但我可以推测这个反例:
struct S {
struct S2 {
S s;
};
S2 s2;
};
在这种情况下,尝试扫描S
并在扫描S2
之前确定其大小将失败。我怀疑递归可能会给编译器带来困难,而这些困难往往是标准中规则的原因。
那就是说,我认为你的解决方案不适合你所描述的问题。我会在S2
上设置S
模板,而不是嵌套。然后我可以使用引用S&
作为模板参数,而不是S
本身。因此,范围将由两个整数和对原始数组对象的引用表示。从这个范围制作一个新的副本将是一个单独的操作。
答案 3 :(得分:0)
编译器无法计算所包含类的大小。
你可以写S* s;
。
答案 4 :(得分:0)
这个答案主要关注两个例子之间的差异,因为其他答案已经解释了为什么第一个例子不起作用。
简而言之,您的第一个示例使用S
中的S
,其方式需要S
完成。
您的第二个示例需要在编译函数体后立即完成C
。您的代码等同于
struct C {
struct C1 {
C makeC();
};
};
inline C C::C1::makeC() {
return C();
}
这意味着,编译器会自动“推迟”函数体。对于函数声明,编译器只需知道C
是一种类型,但如果C
不完整,那么它就可以工作了。在编译函数定义时,类型C
现已完成。