Skip to content

Instantly share code, notes, and snippets.

@zhehaowang
Last active October 26, 2017 03:04
Show Gist options
  • Save zhehaowang/2f7ac74a70094beed67d2cd2dfe3296b to your computer and use it in GitHub Desktop.
Save zhehaowang/2f7ac74a70094beed67d2cd2dfe3296b to your computer and use it in GitHub Desktop.
Gist for testing returning an object (RVO).
// Apple LLVM version 8.0.0 (clang-800.0.42.1)
// g++ -o test test_return_object.cpp
// ./test
#include <iostream>
int g_int = 0;
class Test {
public:
Test() {
d_test = new int(g_int);
std::cout << "construct: " << *d_test << std::endl;
g_int ++;
}
~Test() {
std::cout << "delete: " << *d_test << std::endl;
delete d_test;
}
Test getNewTest() {
Test t1;
// try toggling the "if" line and see magic happen!
if (false) { return t1; }
Test t2;
return t2;
}
int* d_test;
};
int main() {
Test t0;
std::cout << "before getting new\n";
Test t2 = t0.getNewTest();
std::cout << "after getting new\n";
std::cout << "the result is " << *(t2.d_test) << std::endl;
}
// without the if line:
/*
construct: 0
before getting new
construct: 1
construct: 2
delete: 1
after getting new
the result is 2
delete: 2
delete: 0
*/
// with the if line
/*
construct: 0
before getting new
construct: 1
construct: 2
delete: 2
delete: 1
after getting new
the result is 0
delete: 0
test(28284,0x7fff767e6000) malloc: *** error for object 0x7faa93c032c0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
*/
// Notice the difference between when the "delete: 2" happens.
// My explanation:
/*
Standard: compiler has the freedom to choose to carry out RVO or not.
When there is only one return statement (for returning an object), compiler decides to do RVO, in which case no copies are made when returning this object (!), and destructor is not called. Thus the delete statement in destructor is not called, and everything works just fine.
However when there are more than one return statements, even when some statements could've been removed by compiler optimization (off in this case), compiler decides it cannot do RVO, and instead makes a copy: call the destructor, make a new object, and return. Presumably, the copy it makes contains the same reference to the memory block which is now freed by destructor. Thus when later on the copy tries to refer to that memory block, things blow up. (While integer values in the copy would work just fine)
One mystery: it seems that while making the copy, the function calls your destructor explicitly, but not necessarily your constructor (turns out, it calls the copy constructor).
(I tried adding side effect to the constructor, and neither plink nor clang on my Mac executed the side effects)
These all lead to the question: given the presence of RVO, what would be the best practice for returning an object, and having side effects in constructors / destructors (side effect in the sense of "global state" modification, say, cout, memory (de)allocations, or global variable modification)?
*/
/* References:
https://en.wikipedia.org/wiki/Return_value_optimization
https://stackoverflow.com/questions/10476665/c-avoiding-copy-with-the-return-statement#answer-10479595
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment