2

我有一个按值返回对象的函数。接收者变量需要调用该对象上的外向转换运算符。如果我在返回语句 (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 Ref\n";
    return retVal;
}

Ref bar() {
    int * temp = new int(5);
    std::cout << "Return anonymous Ref\n";
    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* 的转换。

4

1 回答 1

2

到底是怎么回事

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

发生这种情况是因为,显然,MSVC 在调试模式下不执行 RVO,因此Ref(Ref&)调用“复制构造函数”()从foo函数返回并执行:

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

其中iptr, 类型int*, 使用从 的隐式转换进行初始化ref

正如@bogdan让我注意到的那样,您的这个“复制构造函数”确实具有移动构造函数语义,您可能应该为此编写一个特定Ref(Ref&&)的。

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

当临时对象超出范围时,指针为deleted,并且当构造的对象 frombar也超出范围时,相同的指针被删除,导致未定义的行为(这是您崩溃的原因)。

编写该类的 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

这里的重点是隐式转换和手动动态内存分配通常会导致许多错误和“崩溃”。尽可能避免他们像瘟疫一样。

于 2015-05-20T10:09:07.147 回答