I have a function which returns an object by value. The recipient variable requires the outward conversion operator on that object to be called. If I construct the returned object at the return statement (RVO) its destructor gets called before the outward conversion operator. However, if I name the object, and return that, the outward conversion operator gets called before the object is destructed. Why is that?
#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;
}
Output from this is:
********* 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 . . .
When bar() is called the reference is deleted before the conversion operator is called, and it crashes. Also, I don't understand why the Ref to int* conversion is getting called when the return value is being built.
I don't understand why the Ref to int* conversion is getting called when the return value is being built.
That happens because, apparently, MSVC does not perform RVO in debug mode, so the "copy constructor" (Ref(Ref&)
) gets called to return from the foo
function and this gets executed:
Ref(Ref & ref) : iptr(ref) {
// ...
}
where iptr
, of type int*
, is initialized with the implicit conversion from ref
.
As @bogdan made me notice, this "copy constructor" of yours really has a move constructor semantic, and you should probably write a specific Ref(Ref&&)
for that instead.
In bar
, we have that you are building an rvalue return Ref(temp)
which cannot be bound to a lvalue reference for the Ref(Ref&)
constructor, and therefore the other constructor is picked and the pointer in copied in (without resetting the temporary one).
When the temporary one gets out of scope, the pointer is delete
d, and when the constructed object from bar
also goes out of scope, the same pointer is deleted, causing undefined behaviour (which is the reason of your crash).
Your class has many other issues. For one it can lead to various crashes and it's generally not memory safe. In C++ you would write something like this for that 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;
};
or even just std::unique_ptr
.
The point here is that implicit conversions and manual dynamic memory allocation will often lead to many bugs and "crashes". Avoid them like the plague as much as possible.