C++ Destructor thru reference - c++

I want to share with you a tiny problem that I'm not getting to work out, here is the code (it's for test only):
#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <type_traits>
#include <sstream>
struct Procedure {
Procedure(HANDLE)
{ std::cout << "ctor w/connection: " << this << std::endl; }
~Procedure()
{ std::cout << "dtor: " << this << std::endl; }
Procedure(Procedure &&rhs) {
std::cout << "ctor w/move: " << this << std::endl;
this->m_Params = std::move(rhs.m_Params);
}
Procedure& operator= (Procedure &&rhs) {
std::cout << "operator= w/move: " << this << std::endl;
if (this != &rhs) this->m_Params = std::move(rhs.m_Params);
return *this;
}
Procedure& AppendParam(const std::string &str) {
std::cout << "appendparam: " << this << std::endl;
m_Params.push_back(str);
return *this;
}
void Execute( const std::string &str) {
std::stringstream ss;
ss << str << '(';
for (int i = 0, mx = m_Params.size(); i < mx; ++i) {
ss << '\'' << m_Params[i] << '\'';
if (i < mx - 1) ss << ',';
}
ss << ");";
std::cout << "calling: " << this << " : " << ss.str() << std::endl;
}
private:
Procedure(const Procedure &) = delete;
Procedure& operator=(const Procedure &) = delete;
std::vector<std::string> m_Params;
};
Procedure ProcedureCaller()
{ return Procedure(nullptr); }
int __cdecl main() {
std::cout << "test1---------------------" << std::endl; {
auto &proc = ProcedureCaller().AppendParam("param_1").AppendParam("param_2");
proc.Execute("sp_test");
}
std::cout << "test2--------------------" << std::endl; {
auto proc = ProcedureCaller();
proc.AppendParam("param_A").AppendParam("param_B");
proc.Execute("sp_test_2");
}
std::cout << "test3--------------------" << std::endl; {
ProcedureCaller().AppendParam("param_AA").AppendParam("param_BB").Execute("sp_test_2");
}
return 0;
}
And here is the result I'm getting:
test1---------------------
ctor w/connection: 00F8FC98
appendparam: 00F8FC98
appendparam: 00F8FC98
dtor: 00F8FC98
calling: 00F8FC98 : sp_test();
test2--------------------
ctor w/connection: 00F8FD70
appendparam: 00F8FD70
appendparam: 00F8FD70
calling: 00F8FD70 : sp_test_2('param_A','param_B');
dtor: 00F8FD70
test3--------------------
ctor w/connection: 004FFB20
appendparam: 004FFB20
appendparam: 004FFB20
calling: 004FFB20 : sp_test_2('param_AA','param_BB');
dtor: 004FFB20
I have a few questions:
1- Why dtor of "test1" is getting called before the end of its scope? I mean, the code hasn't even called the Execute method.
2- If dtor of "test1" is a temporal object, why I'm not seeing a log from the move ctor, or at least a compiler error because it's trying to use the deleted copy ctor?
3- What's the difference between "test1" and "test2", I want to be able to call the Execute whatever way I want.
4- What am I missing?
Thanks.

Here's a simpler version demonstrating the same problem:
struct X {
X() = default;
~X() { std::cout << "dtor\n"; }
X& self() { return *this; }
};
int main()
{
X& x = X().self();
std::cout << "here?\n";
}
This program prints dtor before it prints here. Why? The problem is, we have a temporary (X()) that does not get lifetime extended, so it gets destroyed at the end of the expression that contains it (which is X().self()). While you get a reference to it, it's not one of the magic references that does lifetime extension - what you get is just a reference to an object that's immediately going out of scope.
Lifetime extension only happens under very limited circumstances. The temporary has to be bound immediately to a reference, which can only happen for const references:
X const& x = X();
std::cout << "here\n";
Now this prints here before dtor.
Additionally, there is no transitive lifetime extension. Even if in the original example we did:
X const& x = X().self();
We'd still get a dangling reference.

In the "test1" case, the object referenced by proc is a temporary. It goes out of scope at the end of the full expression in which it was created, and proc is immediately left dangling. Note that no lifetime extension happens for two reasons: a) lifetime extension only happens with const lvalue-references and rvalue-references and b) lifetime extension only happens with prvalues, while the reference returned by AppendParam is an lvalue.
proc in the "test1" case is a reference, not an object. No move or copy happens because there is no object to be moved or copied to. A reference is bound to the temporary object returned by ProcedureCaller, and it goes out of scope at the next ;.
The difference between "test1" and "test2" is that proc is a reference in "test1" and an actual object in "test2". If you tried to make proc a reference in the "test2" case the compiler would complain. Only const lvalue-references and rvalue-references may be bound to a prvalue (such as the object returned from a function). The only reason it works in the "test1" case is that you've "laundered" the prvalue through a method that returns an lvalue-reference.

Related

Will moving from released pointers leak memory?

I have the following code:
std::unique_ptr<T> first = Get();
…
T* ptr_to_class_member = GetPtr(obj);
*ptr_to_class_member = std::move(*first.release());
Will this behave as expected with no copies, 1 move and without memory leak?
*ptr_to_class_member = std::move(*first.release());
just calls the move assignment operator of T with the object pointed to by first as argument. This may properly transfer some data, but delete is not called or the object so neither T::~T is executed nor does the memory of the object get freed.
In the example of T = std::string this would result in the backing storage of the string object properly being transfered from the rhs to the lhs of the move assignment, but dynamically allocated memory of size sizeof(std::string) would still be leaked.
For some classes the lack of a destructor invocation for the object could result in additional trouble, since move assignment simply needs to leave the rhs in an unspecified, but valid state which could still require freeing of additional resources.
You need to do
*ptr_to_class_member = std::move(*first);
first.reset();
in order to prevent memory leaks.
To show what's going wrong here, the following code implements prints for memory (de)allocation and special member functions:
#include <iostream>
#include <memory>
#include <new>
#include <utility>
struct TestObject
{
TestObject()
{
std::cout << "TestObject::TestObject() : " << this << '\n';
}
TestObject(TestObject&& other)
{
std::cout << "TestObject::TestObject(TestObject&&) : " << this << ", " << &other << '\n';
}
TestObject& operator=(TestObject&& other)
{
std::cout << "TestObject::operator=(TestObject&&) : " << this << ", " << &other << '\n';
return *this;
}
~TestObject()
{
std::cout << "TestObject::~TestObject() : " << this << '\n';
}
void* operator new(size_t size)
{
void* const result = ::operator new(size);
std::cout << "memory allocated for TestObject: " << result << '\n';
return result;
}
void operator delete(void* mem)
{
std::cout << "memory of TestObject deallocated: " << mem << '\n';
::operator delete(mem);
}
};
template<class Free>
void Test(Free free, char const* testName)
{
std::cout << testName << " begin -------------------------------------------\n";
{
auto ptr = std::make_unique<TestObject>();
std::cout << "object creation done\n";
free(ptr);
}
std::cout << testName << " end ---------------------------------------------\n";
}
int main()
{
TestObject lhs;
Test([&lhs](std::unique_ptr<TestObject>& ptr)
{
lhs = std::move(*ptr);
ptr.reset();
}, "good");
Test([&lhs](std::unique_ptr<TestObject>& ptr)
{
lhs = std::move(*ptr.release());
}, "bad");
}
Possible output:
TestObject::TestObject() : 0000009857AFF994
good begin -------------------------------------------
memory allocated for TestObject: 000001C1D5715EF0
TestObject::TestObject() : 000001C1D5715EF0
object creation done
TestObject::operator=(TestObject&&) : 0000009857AFF994, 000001C1D5715EF0
TestObject::~TestObject() : 000001C1D5715EF0
memory of TestObject deallocated: 000001C1D5715EF0
good end ---------------------------------------------
bad begin -------------------------------------------
memory allocated for TestObject: 000001C1D5715EF0
TestObject::TestObject() : 000001C1D5715EF0
object creation done
TestObject::operator=(TestObject&&) : 0000009857AFF994, 000001C1D5715EF0
bad end ---------------------------------------------
TestObject::~TestObject() : 0000009857AFF994
You can clearly see the destructor call and deallocation missing in the second case, which is the one matching the code you're asking about.

How to avoid dangling reference with R-value

Some discussion warns about dangling reference, with R-Value reference. I do not see any dangling reference in the following example as DTOR was called when main() terminates. Am I missing something?
class test_ctor
{
public:
explicit
test_ctor(int x = 1):_x(x)
{
std::cout << "CTOR: " << _x << "\n";
}
~test_ctor()
{
std::cout << "DTOR: " << _x << "\n";
}
test_ctor(test_ctor const & y) = default;
test_ctor(test_ctor && y) = default;
int _x;
};
test_ctor test_rvalue()
{
test_ctor test = test_ctor(2);
return test;
}
Now I can use the above code in two ways:
int main(int argc, const char * argv[]) {
auto test = test_rvalue();
std::cout << " test: " << test._x << " \n";
return 0;
}
Or
int main(int argc, const char * argv[]) {
auto && test = test_rvalue();
std::cout << " test: " << test._x << " \n";
return 0;
}
both case has the same output:
CTOR: 2
test: 2
DTOR: 2
Which means both are efficient ways to return object. Are there any side effects in r-value reference?
In c++11:
auto test = test_rvalue(); moves the return value of test_rvalue() into test. This move is then elided by every non-brain damaged compiler so it never happens. The move constructor still needs to exist, but is never called, and any side effects of moving do not occur.
auto&& test = test_rvalue(); binds an rvalue reference to the temporary returned by test_rvalue(). The temporaries lifetime is extended to match that of the reference.
In c++17:
auto test = test_rvalue(); the prvalue return value of test_rvalue() is used to directly construct the variable test. No move, elided or not, occurs.
The auto&& case remains unchanged from c++11.

Unexpected lifetime of lambda in pipeline

I'm using a pipeline of ranges views and I want to do a complete transformation of the output and keep the pipeline going. I understand that I can't return a copy of the new range because it wouldn't live anywhere. What I don't understand is why I can't keep the storage in the lambda's closure.
auto counter = []() {
std::map<int, int> counts;
return ranges::make_pipeable([=](auto &&rng) mutable -> std::map<int, int>& {
// Do stuff that fills in the map
return counts;
});
};
auto steps = foo() | counter() | bar();
auto data = // stuff
ranges::for_each(data | steps, [](auto &&i) {
std::cout << i << std::endl;
});
When I do a release build this works, but when I do a debug build this segfaults. If I change the map in the counter lambda to be static and the capture to be by reference then this works of course (but is clearly ugly as anything).
What I don't really understand is why the lifetime of the lambda returned from counter (and it's attendant closure) isn't at least as long as the lifetime of the variable steps.
That's because make_pipeable takes it argument by value. Here is the definition from the src code:
struct make_pipeable_fn
{
template<typename Fun>
detail::pipeable_binder<Fun> operator()(Fun fun) const
{
return {std::move(fun)};
}
};
Since you are returning std::map<int, int> by reference, it is pointing to an address of an object which has been moved from. (std::move in the above call)
If this is not super clear, consider a simpler example. In the below code, Noisy is just a struct which emits out its calls.
#include <iostream>
#include <range/v3/utility/functional.hpp>
using namespace ranges;
struct Noisy
{
Noisy() { local_ = ++cnt_; std::cout << "Noisy() ctor " << local_ << '\n'; }
Noisy(const Noisy&) { local_= ++cnt_; std::cout << "Noisy(Noisy&) copy ctor " << local_ << '\n'; }
Noisy(Noisy&&) { local_ = ++cnt_; std::cout << "Move constructor " << local_ << '\n'; }
~Noisy() { std::cout << "~Noisy() dtor with local " << local_ << '\n'; }
// global object counter
static int cnt_;
// local count idx
int local_ = 0;
};
int Noisy::cnt_ = 0;
auto counter = []()
{
Noisy n;
return make_pipeable([=](auto x) { return n; });
};
int main(int argc, char *argv[])
{
auto steps = counter();
std::cout << "Deleting" << '\n';
return 0;
}
The output of the above code is this:
Noisy() ctor 1
Noisy(Noisy&) copy ctor 2
Move constructor 3
Move constructor 4
~Noisy() dtor with local 3
~Noisy() dtor with local 2
~Noisy() dtor with local 1
Deleting
~Noisy() dtor with local 4
As you can see, object 2 (which in your case is holding the std::map<int, int>) is destroyed before the line printing Deleting. steps is a pipeable_binder struct which extends from our passed lambda and is destroyed at the end.
So I took #skgbanga's code and re-wrote it so it works more like the actual situation. This shows that the application of the steps to the data results in extra copies of the lambdas captured by the pipeline.
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
struct Noisy {
Noisy() { local_ = ++cnt_; std::cout << "Noisy() ctor " << local_ << '\n'; }
Noisy(const Noisy&) { local_= ++cnt_; std::cout << "Noisy(Noisy&) copy ctor " << local_ << '\n'; }
Noisy(Noisy&&) { local_ = ++cnt_; std::cout << "Move constructor " << local_ << '\n'; }
~Noisy() { std::cout << "~Noisy() dtor with local " << local_ << '\n'; }
// global object counter
static int cnt_;
// local count idx
int local_ = 0;
// Make this a range as well. Empty is fine
std::vector<int> range;
auto begin() { return range.begin(); }
auto end() { return range.end(); }
};
int Noisy::cnt_ = 0;
auto counter = []() {
Noisy n;
return ranges::make_pipeable([=](auto x) mutable -> Noisy& {
std::cout << "Returning Noisy range " << n.local_ << '\n';
return n;
});
};
int main(int argc, char *argv[])
{
auto steps = counter();
std::vector<int> data{{1, 2}};
std::cout << "Applying" << '\n';
auto result = data | steps;
std::cout << "Displaying" << '\n';
ranges::for_each(result, [](auto&& v) {
std::cout << "Local result " << v << '\n';
});
std::cout << "Deleting" << '\n';
return 0;
}
The output is:
Noisy() ctor 1
Noisy(Noisy&) copy ctor 2
Move constructor 3
Move constructor 4
~Noisy() dtor with local 3
~Noisy() dtor with local 2
~Noisy() dtor with local 1
Applying
Noisy(Noisy&) copy ctor 5
Noisy(Noisy&) copy ctor 6
Returning Noisy range 6
~Noisy() dtor with local 6
Noisy(Noisy&) copy ctor 7
~Noisy() dtor with local 5
Displaying
Deleting
~Noisy() dtor with local 7
~Noisy() dtor with local 4
Noisy 6 is the one that returns the range and because it was a copy used in the evaluation of the pipe it is almost immediately destructed. This then causes the segfault.
I expect that the extra copies shouldn't really happen.

Move constructor called with lambda

I am trying to understand how lambdas work in C++ in depth. I have written the following piece of code.
#include <iostream>
#include <functional>
struct A
{
A() { std::cout << "A" << (data = ++count) << ' '; }
A(const A& a) { std::cout << "cA" << (data = a.data + 20) << ' '; }
A(A&& a) { std::cout << "mA" << (data = std::move(a.data) + 10) << ' '; }
~A() { std::cout << "dA" << data << ' '; }
int data;
static int count;
};
int A::count = 0;
void f(A& a, std::function<void(A)> f)
{
std::cout << "( ";
f(a);
std::cout << ") ";
}
int main()
{
A temp, x;
auto fun = [=](A a) {std::cout << a.data << '|' << x.data << ' ';};
std::cout << "| ";
f(temp, fun);
std::cout << "| ";
}
The output is below.
A1 A2 cA22 | cA42 mA52 dA42 ( cA21 mA31 31|52 dA31 dA21 ) dA52 | dA22 dA2 dA1
This is quite clear to me, except for the 'mA52' move constructor call. Note that I am using variable capture by value, so without the move constructor, the copy-constructor would be called here. Why is there an additional copy/move at this step? One would expect the object to be copied only once when fun is passed by value as an argument to f. Furthermore, the first copy of the object is immediately destroyed. Why? What is this intermediary copy?
Let's call your lambda type L. It's unnamed, but it gets confusing to refer to it without a name.
The constructor std::function<void(A)>(L l) takes L by value. This involves creating a copy of the original fun.
The constructor then moves the lambda from l into some storage managed by the std::function<void(A)> wrapper. That move also involves moving any captured entities.
std::function<void(A)> takes the function object you pass to it by value (this is the cA42 in your output). It then moves the function object in to its internal storage (this is the mA52).

Copy elision on Visual C++ 2010 Beta 2

I was reading Want Speed? Pass by Value on the C++ Next blog and created this program to get a feel for copy elision and move semantics in C++0x:
#include <vector>
#include <iostream>
class MoveableClass {
public:
MoveableClass() : m_simpleData(0), instance(++Instances) {
std::cout << "Construct instance " << instance << " (no data)" << std::endl;
}
MoveableClass(std::vector<double> data) : m_data(std::move(data)), m_simpleData(0), instance(++Instances) {
std::cout << "Construct instance " << instance << " (with data)" << std::endl;
}
MoveableClass(int simpleData) : m_simpleData(simpleData), instance(++Instances) {
std::cout << "Construct instance " << instance << " (with simple data)" << std::endl;
}
MoveableClass(const MoveableClass& other)
: m_data(other.m_data), m_simpleData(other.m_simpleData), instance(++Instances)
{
std::cout << "Construct instance " << instance << " from a copy of " << other.instance << std::endl;
Elided = false;
}
MoveableClass(MoveableClass&& other)
: m_data(std::move(other.m_data)), m_simpleData(other.m_simpleData), instance(++Instances)
{
std::cout << "Construct instance " << instance << " from a move of " << other.instance << std::endl;
Elided = false;
}
MoveableClass& operator=(MoveableClass other) {
std::cout << "Assign to instance " << instance << " from " << other.instance << std::endl;
other.Swap(*this);
return *this;
}
~MoveableClass() {
std::cout << "Destroy instance " << instance << std::endl;
--Instances;
}
void Swap(MoveableClass& other) {
std::swap(m_data, other.m_data);
std::swap(m_simpleData, other.m_simpleData);
}
static int Instances;
static bool Elided;
private:
int instance;
int m_simpleData;
std::vector<double> m_data;
};
int MoveableClass::Instances = 0;
bool MoveableClass::Elided = true;
std::vector<double> BunchOfData() {
return std::vector<double>(9999999);
}
int SimpleData() {
return 9999999;
}
MoveableClass CreateRVO() {
return MoveableClass(BunchOfData());
}
MoveableClass CreateNRVO() {
MoveableClass named(BunchOfData());
return named;
}
MoveableClass CreateRVO_Simple() {
return MoveableClass(SimpleData());
}
MoveableClass CreateNRVO_Simple() {
MoveableClass named(SimpleData());
return named;
}
int main(int argc, char* argv[]) {
std::cout << "\nMove assign from RVO: " << '\n';
{
MoveableClass a;
a = CreateRVO();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
std::cout << "\nMove assign from RVO simple: " << '\n';
{
MoveableClass a;
a = CreateRVO_Simple();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
std::cout << "\nMove assign from NRVO: " << '\n';
{
MoveableClass a;
a = CreateNRVO();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
std::cout << "\nMove assign from NRVO simple: " << std::endl;
{
MoveableClass a;
a = CreateNRVO_Simple();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
}
Here is the output I get when compiled in release mode on Visual C++ 10.0 (Beta 2):
Move assign from RVO:
Construct instance 1 (no data)
Construct instance 2 (with data)
Construct instance 3 from a move of 2
Destroy instance 2
Assign to instance 1 from 3
Destroy instance 3
Destroy instance 1
Move elided: No
Move assign from RVO simple:
Construct instance 1 (no data)
Construct instance 2 (with simple data)
Assign to instance 1 from 2
Destroy instance 2
Destroy instance 1
Move elided: Yes
Move assign from NRVO:
Construct instance 1 (no data)
Construct instance 2 (with data)
Assign to instance 1 from 2
Destroy instance 2
Destroy instance 1
Move elided: Yes
Move assign from NRVO simple:
Construct instance 1 (no data)
Construct instance 2 (with simple data)
Assign to instance 1 from 2
Destroy instance 2
Destroy instance 1
Move elided: Yes
However, I am perplexed by one thing. As you can see, all of the moves are elided except for the first one. Why can't the compiler perform RVO with a MoveableClass(std::vector) at line 86, but can with a MoveableClass(int) at line 97? Is this just a bug with MSVC or is there a good reason for this? And if there is a good reason, why can it still perform NRVO on a MoveableClass(std::vector) at line 91?
I'd like to understand it so I can go to sleep happy. :)
Thanks for replying Dave.
I've added my tests to that example:
pastebin.com/f7c8ca0d6
Curiously it shows that all types of elisions are not being performed except for NRVO!
Edit: Actually I suppose this is because it is the only test where the object ever has a name.
I also tried other STL types and got the same result. However when trying my own non-pod types it works as expected. I can't think what's special about the STL types that could be causing this so I don't know what else to try.
I'll submit a bug report.
Edit: Submitted here
Thanks
Hmm.
It seems that if you change the data constructor
MoveableClass::MoveableClass(std::vector<double> data)
to accept the vector by reference, like so,
MoveableClass::MoveableClass(const std::vector<double>& data)
it works fine! Why does it not work if you pass the vector by value?
Also here's a version that should compile on earlier versions of MSVC, if anybody wants to run the test there. It contains no C++0x features: http://pastebin.com/f3bcb6ed1
Maybe it'd be a good idea to update and maintain this example from cpp-next with a version of your test that fails, so there can be one comprehensive, canonical test.