为什么值返回的未命名对象在调用其转换运算符之前被破坏?
Why does un-named object returned by value get destructed before its conversion operator is called?
我有一个函数,它 return 按值生成一个对象。接收者变量需要调用该对象上的向外转换运算符。
如果我在 return 语句 (RVO) 中构造 returned 对象,则在向外转换运算符之前调用其析构函数。
但是,如果我命名该对象,并且 return 那个,则在对象被破坏之前调用向外转换运算符。
这是为什么?
#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() 时,在调用转换运算符之前删除了引用,它崩溃了。
另外,我不明白为什么在构建 return 值时会调用 Ref 到 int* 的转换。
这是怎么回事
I don't understand why the Ref to int* conversion is getting called when the return value is being built.
发生这种情况的原因显然是 MSVC 不在调试模式下执行 RVO,因此“复制构造函数”(Ref(Ref&)
) 从 foo
函数调用到 return,并且这被执行:
Ref(Ref & ref) : iptr(ref) {
// ...
}
其中类型 int*
的 iptr
通过 ref
.
的隐式转换进行初始化
正如 @bogdan 让我注意到的那样,您的这个“复制构造函数”确实具有移动构造函数语义,您可能应该为此编写一个特定的 Ref(Ref&&)
。
在 bar
中,我们知道您正在构建一个右值 return Ref(temp)
,它不能绑定到 Ref(Ref&)
构造函数的左值引用,因此选择了另一个构造函数并复制的指针(不重置临时指针)。
当临时对象超出范围时,指针为delete
d,当bar
构造的对象也超出范围时,删除相同的指针,导致未定义行为(这就是你崩溃的原因)。
C++ 的写法class
您的 class 还有许多其他问题。一方面,它会导致各种崩溃,而且它通常不是内存安全的。在 C++ 中,你会为 class:
写这样的东西
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
.
这里的重点是隐式转换和手动动态内存分配通常会导致许多错误和“崩溃”。尽可能避开它们,就像避开瘟疫一样。
我有一个函数,它 return 按值生成一个对象。接收者变量需要调用该对象上的向外转换运算符。 如果我在 return 语句 (RVO) 中构造 returned 对象,则在向外转换运算符之前调用其析构函数。 但是,如果我命名该对象,并且 return 那个,则在对象被破坏之前调用向外转换运算符。 这是为什么?
#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() 时,在调用转换运算符之前删除了引用,它崩溃了。 另外,我不明白为什么在构建 return 值时会调用 Ref 到 int* 的转换。
这是怎么回事
I don't understand why the Ref to int* conversion is getting called when the return value is being built.
发生这种情况的原因显然是 MSVC 不在调试模式下执行 RVO,因此“复制构造函数”(Ref(Ref&)
) 从 foo
函数调用到 return,并且这被执行:
Ref(Ref & ref) : iptr(ref) {
// ...
}
其中类型 int*
的 iptr
通过 ref
.
正如 @bogdan 让我注意到的那样,您的这个“复制构造函数”确实具有移动构造函数语义,您可能应该为此编写一个特定的 Ref(Ref&&)
。
在 bar
中,我们知道您正在构建一个右值 return Ref(temp)
,它不能绑定到 Ref(Ref&)
构造函数的左值引用,因此选择了另一个构造函数并复制的指针(不重置临时指针)。
当临时对象超出范围时,指针为delete
d,当bar
构造的对象也超出范围时,删除相同的指针,导致未定义行为(这就是你崩溃的原因)。
C++ 的写法class
您的 class 还有许多其他问题。一方面,它会导致各种崩溃,而且它通常不是内存安全的。在 C++ 中,你会为 class:
写这样的东西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
.
这里的重点是隐式转换和手动动态内存分配通常会导致许多错误和“崩溃”。尽可能避开它们,就像避开瘟疫一样。