我无法理解此复制构造函数行为

I can't understand this copy constructor behaviour

提问人:Zebrafish 提问时间:9/27/2017 更新时间:9/28/2017 访问量:121

问:

我有一些奇怪的行为,如下:

using namespace std;
struct Number
{
    Number(int init) : a(init) {}
    Number() {};
    int a;
    static Number& getNumber() { return Number(555); }

    //Number(const Number& other)
    //{
    //  a = other.a;
    //}  // I've commented this out you'll see why
};

int main()
{
    Number num1;  // Is now junk
    Number num2;  // Is now junk
    num2 = Number::getNumber(); // num 2 is still junk

    Number num3 = Number::getNumber(); // num3 has been assigned 555. 
                                       // If I define my own copy constructor
                                       // (uncomment it), it stays junk.
    cout << num3.a;
}

当我制作自己的复制构造函数时,无论它是否采用常量,“other”参数中的值都是垃圾。如果复制构造函数是默认构造函数,则我不会出现这种行为。我使用 GCC 在 IDEOne 上尝试过,但此代码无法编译。但是,在我的 Visual Studio 上,它按照我的描述运行。

我发现很难理解临时有效期的规则。例如,我认为如果 getNumber() 返回对本地临时的引用,那么直接在同一行上分配它是可以的。我错了。

C++ 引用 复制构造函数 生存期

评论

1赞 Galik 9/27/2017
静态函数通过引用返回一个临时对象。所以它在使用之前就被销毁了。
0赞 NathanOliver 9/27/2017
如果 gcc 无法编译,只需停止并收听其错误消息即可。MSVS有一个“功能”,允许它编译。
0赞 Zebrafish 9/27/2017
@NathanOliver 这真是邪恶,我想要这个警告
0赞 Benjamin Lindley 9/27/2017
@Zebrafish:使用标志。如果您在 IDE 中,请转到主菜单/ZaProject->Properties->C/C++->Language->Disable Language Extensions->Yes
0赞 Pete Becker 9/27/2017
对于像 这样的简单值类型,通常应按值传递它们,而不是按引用传递它们。(请注意,复制构造函数必须通过引用来获取其参数;否则将得到无限递归)Number

答:

0赞 François Andrieux 9/27/2017 #1

该函数创建一个临时函数并返回对它的引用。临时对象在函数结束时不复存在,这意味着您现在返回的引用是指已销毁的对象。这是未定义的行为,这意味着该行为可以是任何东西,包括有时看起来有效。编译器不需要诊断此错误,但某些编译器(如 GCC)需要诊断此错误。如果要返回对共享实例的可变引用,请在函数正文中声明一个静态本地对象,并返回对它的引用。static Number& getNumber() { return Number(555); }Number

static Number& getNumber() 
{ 
    static Number my_instance{555}; 
    return my_instance;
}
2赞 NathanOliver 9/27/2017 #2

getNumber具有未定义的行为。您正在返回对本地对象的引用。当函数返回该对象时,该对象被销毁,因此现在您具有对不再存在的对象的引用。为了解决这个问题,我们可以按值返回,例如

static Number getNumber() { return {555}; }

现在,返回的数字是直接从返回值构造的。

在创建返回值之后,但在执行继续之前,所有函数局部变量都会被销毁。这意味着返回任何类型的引用或指向本地对象的指针都会给你留下一个悬空的引用/指针。

评论

0赞 Zebrafish 9/27/2017
不,我知道 num2 在这种情况下使用赋值运算符。所以,我知道临时本地被销毁了,但我认为如果它直接在同一行上分配,它将分配正确的值。我错了吗?换句话说,本地在返回的那一刻就不好了,无论它是否被分配。
0赞 NathanOliver 9/27/2017
@Zebrafish 是的。函数 local 参数在获得要分配给的引用后立即被销毁。在表达式中创建的临时值将持续到完整表达式的末尾,但不包括函数局部参数。当函数返回时和执行继续进行之前,它们总是被销毁。num2
1赞 Jack V. 9/27/2017 #3

我学到了一些东西来试图回答这个问题。

从静态函数返回对象的可能方法有哪些?

按值

这通常是正确的方法。通常,编译器会使用 Return-Value-Optimisation 或以其他方式避免多次实际复制对象。如果您不确定这可能是您想要的。

如果默认的复制构造函数对你来说还不够,请确保你定义了自己的构造函数,记住要定义它以采用 const ref 参数。如果您使用的是 C++ 11,则定义单独的移动构造函数可能很有用。

通过非常量引用

这是不正确的,因为它是指向内存位置的引用(实际上是指针),该位置曾经包含不再存在的变量。

这是 gcc 中的错误。它曾经在 Visual Studio 中被允许,尽管我听说它可能不再允许了。如果需要,可以使用编译器选项 /Za 进行编译,以关闭各种特定于 Microsoft 的扩展。

通过常量引用

这不是错误,而是警告,是未定义的行为 [需要引证]

您可以将 const ref 绑定到临时对象。参见 Herb Sutter 的文章:https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

例如,“const Number&num = get_number_by_value()”通常会返回一个临时对象的副本,并省略该副本,然后临时对象将被绑定到引用,并以专门用于 const ref(但不是其他引用或指针)的方式延长其生存期。

但是,我现在才知道,从技术上讲,这适用于从函数返回临时函数,但如果将其分配给另一个常量引用,则该生命周期不会进一步延长。

所以你的情况

Number num = get_number_by_const_ref()

可能工作正常,但

const Number& num = get_number_by_const_ref()

可能不会。

返回对静态成员变量的常量引用

这通常没有帮助,但是如果你的类的构造成本非常高(需要大量计算或使用 GB 内存),并且你想多次返回它的特定实例,你可能有一个类的私有 const static 成员变量,它存储一个你可以通过 ref 返回的实例。

请记住,如果您有一个包含类的实例变量的静态成员变量,则需要在类外部的 .c 文件中对其进行初始化,以便构造函数可用。

评论

0赞 NathanOliver 9/28/2017
你不能通过 const ref 返回函数局部变量
0赞 Jack V. 9/28/2017
谢谢。我是罪魁祸首。您知道要引用哪一点标准吗?
0赞 NathanOliver 9/28/2017
它不是随手可得的,它包含在范围界定和生存期规则中。不过,这是一篇关于它的好文章。