尽管发布到此线程(已回答所有我的个人问题)的答案数量和评论数量,我还是决定为我们其他人发布一个答案。我最初不明白 OP 的意思,但现在我明白了,所以我想我会分享。亲爱的读者,如果你知道这一切并让你感到厌烦,那么请继续前进。
@xskxzr 和@hvd 有效地回答了这个问题,但@hvd 的帖子特别是标准的,并假设读者知道按值返回(以及扩展RVO)是如何工作的,我想不是每个人都这样做。我以为我做到了,但我错过了一个重要的细节(当你仔细考虑时,实际上很明显,但我仍然错过了)。
所以这篇文章主要关注这个,所以我们都可以看到为什么(a)OP想知道为什么首先编译bar()会出现问题,然后(b)后来才意识到原因。
那么,让我们再看看这段代码。鉴于此(这是合法的,即使类型定义不完整):
class C;
C foo();
为什么编译器不能编译这个(我已经删除了inline,因为它无关紧要):
C bar() { return foo(); }
来自 gcc 的错误信息是:
错误:返回类型“C 类”不完整
好吧,首先,接受的答案引用了明确禁止它的标准中的相关段落,所以这并不神秘。但是 OP(实际上是评论者 Walter,他立即接受了这一点)想知道原因。
起初在我看来很明显:调用者需要为函数结果分配空间,并且它不知道对象有多大,所以编译器陷入困境。但我错过了一个技巧, 在于按值返回的工作方式。
现在对于那些不知道的人来说,按值返回类对象的工作原理是调用者在堆栈上为返回的对象分配空间,并将指向它的指针作为隐藏参数传递给被调用的函数,然后构造对象,操纵它,无论如何。
然而,这个菊花链,所以如果我们有以下代码(我们在调用bar()之前完全定义C):
class C
{
public:
int x;
};
C c = bar ();
c.x = 4;
然后为c 分配空间之前 调用bar() 并将c 的地址作为隐藏参数传递给bar(),然后直接传递给@ 987654335@,最终填充在所需位置构造对象。所以,因为bar() 实际上并没有对这个指针做任何事情(除了传递它),所以它只关心指针本身,而不是它指向的内容。
还是这样?嗯,实际上,是的,确实如此。
当按值返回类对象时,小对象通常在一个寄存器(或一对寄存器)中作为优化返回。在对象足够小的大多数情况下,编译器可以避免这样做(稍后会详细介绍)。
但是现在,bar() 需要知道这是否是 foo() 要做的事情,并且出于各种原因,它需要查看类的完整声明.
所以,总而言之,这就是为什么编译器需要一个完全定义的类型来调用foo(),否则它不会知道foo() 会期待什么,所以它不知道什么代码生成。无论如何,大多数平台上都没有,故事结束。
注意事项:
-
我查看了 gcc,似乎有两个(完全合乎逻辑的)规则来确定一个类对象是在一个寄存器还是一对寄存器中返回:
如果有读者不知道,RVO 依赖于构造对象在其最终的休息地(即在调用者分配的位置) .这是因为有些对象(例如 std::basic_string 的一些实现,我相信)对在内存中移动很敏感,所以你不能只在你方便的地方构建它们,然后在其他地方 memcpy 它们。
如果无法在该最终位置构造返回的对象(因为您编写返回对象的函数的方式),则不会发生 RVO(怎么会发生?),请参见下面的现场演示 ( make_no_RVO())。
作为第 1b 点的一个具体示例,如果一个小对象包含(可能)指向其自身或其任何数据成员的数据成员,那么如果您不这样做,则按值返回它会给您带来麻烦'没有正确声明它。只需添加一个空的复制构造函数就可以了,从那时起它就不再是可复制的了。但是我想这通常是正确的,不要向编译器隐藏重要信息。
现场演示here。欢迎本帖所有的cmet们,我会尽我所能回复他们的。