为什么由value返回的未命名对象在调用其转换操作符之前被析构

Why does un-named object returned by value get destructed before its conversion operator is called?

本文关键字:操作符 转换 析构 调用 value 返回 对象 未命名 为什么      更新时间:2023-10-16

我有一个按值返回对象的函数。接收方变量需要调用该对象上的向外转换操作符。如果在返回语句(RVO)处构造返回对象,则在向外转换操作符之前调用其析构函数。但是,如果我命名对象并返回它,则在对象被析构之前调用向外转换操作符。为什么呢?

#include <iostream>
class Ref {
public:
    Ref(int * ptr) : iptr(ptr) {
        std::cout << "Ref Constructed at: " << long(this) << " Pointing to: " << long(ptr) << 'n';
    }
    Ref(Ref & ref) : iptr(ref) {
        std::cout << "Ref Moved to: " << long(this) << 'n';
        ref.iptr = nullptr;
    }
    operator int () {
        std::cout << "Ref-To int: Temp at: " << long(iptr) << 'n';
        return *iptr;
    }
    operator int* () {
        std::cout << "Ref-To int*: Temp at: " << long(iptr) << 'n';
        return iptr;
    }
    ~Ref() {
        delete iptr;
        std::cout << "Ref at: " << long(this) << " Deleted: " << long(iptr) << 'n';
    }
private:
    int * iptr;
};
Ref foo() {
    int * temp = new int(5);
    Ref retVal(temp); 
    std::cout << "Return named Refn";
    return retVal;
}
Ref bar() {
    int * temp = new int(5);
    std::cout << "Return anonymous Refn";
    return Ref(temp);
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << "*********  Call foo() *************n";   
    int result = foo();
    std::cout << "n*********  Call bar() *************n"; 
    int result2 = bar();
    return 0;
}

输出如下:

*********  Call foo() *************
Ref Constructed at: 2356880 Pointing to: 5470024
Return named Ref
Ref-To int*: Temp at: 5470024
Ref Moved to: 2356956
Ref at: 2356880 Deleted: 0
Ref-To int: Temp at: 5470024
Ref at: 2356956 Deleted: 5470024
*********  Call bar() *************
Return anonymous Ref
Ref Constructed at: 2356680 Pointing to: 5470024
Ref-To int*: Temp at: 5470024
Ref Constructed at: 2356968 Pointing to: 5470024
Ref at: 2356680 Deleted: 5470024
Ref-To int: Temp at: 5470024
Press any key to continue . . .

调用bar()时,在调用转换操作符之前删除引用,并导致崩溃。此外,我不明白为什么在构建返回值时调用Ref到int*转换。

怎么回事

我不明白为什么在构建返回值时调用Ref to int*转换。

这显然是因为MSVC在调试模式下不执行RVO,所以"复制构造函数";(Ref(Ref&))被调用从foo函数返回,这被执行:

Ref(Ref & ref) : iptr(ref) {
    // ...
}

其中int*类型的iptr是用ref的隐式转换初始化的。

@bogdan让我注意到,这个"复制构造函数"的确有一个move构造函数语义,你可能应该为它写一个特定的Ref(Ref&&)

bar中,我们知道你正在构建一个右值return Ref(temp),它不能绑定到Ref(Ref&)构造函数的左值引用,因此选择另一个构造函数并复制指针(不重置临时构造函数)。

当临时指针超出作用域时,指针是 deleted,当从bar构造的对象也超出作用域时,相同的指针被删除,导致未定义的行为(这是崩溃的原因)。

用c++编写该类的方法

你们班还有很多其他问题。首先,它可能导致各种崩溃,而且通常不安全。在c++中,你可以为这个类写这样的代码:

class Ref {
public:
    explicit Ref(std::unique_ptr<int> ptr)
        : iptr(std::move(ptr))
        {}
        
    int get() const { return *iptr; }
    int* data() const { return iptr.get(); }
private:
    std::unique_ptr<int> iptr;
    
};

或者仅仅是std::unique_ptr

这里的要点是隐式转换和手动动态内存分配通常会导致许多错误和"崩溃"。尽量避开它们。