Different behavior with user defined default constructor (only MSVC) - c++

I came across the following problem, with two classes X and Y where Y has a member of type X
If I then:
create a vector of objects of type Y via std::vector<Y> list = {{}}
and Y has a defaulted default constructor
One of the instances of type X will be destroyed twice. Even when using rule of three (or five).
It does not happen, if I either create the vector via {Y{}} or define a user supplied default constructor for Y.
See the console output of the following example:
#include <iostream>
#include <vector>
class X
{
public:
X()
{
std::cout << "constructed " << this << std::endl;
}
~X()
{
std::cout << "deleted " << this << std::endl;
}
X(const X& other)
{
std::cout << "copy constructed " << &other << " to " << this << std::endl;
}
};
class Y
{
public:
//Y(){}; // works
Y() = default; // doesn't work
~Y() = default;
Y(const Y& y) = default; // copy constructor
Y& operator=(const Y& y) = default; // copy assignment
// Y(Y&& other) noexcept = default; // move constructor
// Y& operator=(Y&& other) noexcept = default; // move assignment
private:
X x;
};
int main()
{
std::cout << "start" << std::endl;
std::vector<Y> list = {{}}; // doesn't work
//std::vector<Y> list = {Y{}}; // works
std::cout << "done" << std::endl;
return 0;
}
Outputs
start
constructed 000000271CAFFCD0
copy constructed 000000271CAFFCD0 to 000001727F7145B0
deleted 000000271CAFFCD0
deleted 000000271CAFFCD0
done
deleted 000001727F7145B0
https://godbolt.org/z/vscP4TexE
This is only reproducable in MSVC.
Am I missing something or is this a compiler bug?
Edit
Thanks for the hint about the missing return statements.
I added them, but get the same results.
I did enable warnings, but it only shows that the unreferenced inline functions are removed.
Edit 2
Since they are unused, I removed copy and move constructors and assignment operators from the example.
Edit 3
Adapted Output to the new code example. It still contains the double delete. Note that MSVC is a bit flakey on godbolt and it sometimes does not compile the example.

Related

C++ copy elision of fields

I am trying to get copy elision to work for fields of the object that is to be returned.
Example code:
#include <iostream>
struct A {
bool x;
A(bool x) : x(x) {
std::cout << "A constructed" << std::endl;
}
A(const A &other) : x(other.x) {
std::cout << "A copied" << std::endl;
}
A(A &&other) : x(other.x) {
std::cout << "A moved" << std::endl;
}
A &operator=(const A &other) {
std::cout << "A reassigned" << std::endl;
if (this != &other) {
x = other.x;
}
return *this;
}
};
struct B {
A a;
B(const A &a) : a(a) {
std::cout << "B constructed" << std::endl;
}
B(const B &other) : a(other.a) {
std::cout << "B copied" << std::endl;
}
B(B &&other) : a(other.a) {
std::cout << "B moved" << std::endl;
}
B &operator=(const B &other) {
std::cout << "B reassigned" << std::endl;
if (this != &other) {
a = other.a;
}
return *this;
}
};
B foo() {
return B{A{true}};
}
int main() {
B b = foo();
std::cout << b.a.x << std::endl;
}
I compile with:
g++ -std=c++17 test.cpp -o test.exe
output:
A constructed
A copied
B constructed
1
B is constructed in-place. Why is A not? I would at least expect it to be move-constructed, but it is copied instead.
Is there a way to also construct A in-place, inside the B to be returned? How?
Constructing a B from an A involves copying the A - it says so in your code. That has nothing to do with copy elision in function returns, all of this happens in the (eventual) construction of B. Nothing in the standard allows eliding (as in "breaking the as-if rule for") the copy construction in member initialization lists. See [class.copy.elision] for the handful of circumstances where the as-if rule may be broken.
Put another way: You get the exact same output when creating B b{A{true}};. The function return is exactly as good, but not better.
If you want A to be moved instead of copied, you need a constructor B(A&&) (which then move-constructs the a member).
You will not succeed at eliding that temporary in its current form.
While the language does try to limit the instantiation ("materialisation") of temporaries (in a way that is standard-mandated and doesn't affect the as-if rule), there are still times when your temporary must be materialized, and they include:
[class.temporary]/2.1: - when binding a reference to a prvalue
You're doing that here, in the constructor argument.
In fact, if you look at the example program in that paragraph of the standard, it's pretty much the same as yours and it describes how the temporary needn't be created in main then copied to a new temporary that goes into your function argument… but the temporary is created for that function argument. There's no way around that.
The copy to member then takes place in the usual manner. Now the as-if rule kicks in, and there's simply no exception to that rule that allows B's constructor's semantics (which include presenting "copied" output) to be altered in the way you were hoping.
You can check the assembly output for this, but I'd guess without the output there will be no need to actually perform any copy operations and the compiler can elide your temporary without violating the as-if rule (i.e. in the normal course of its activities when creating a computer program, from your C++, which is just an abstract description of a program). But then that's always been the case, and I guess you know that already.
Of course, if you add a B(A&& a) : a(std::move(a)) {} then you move the object into the member instead, but I guess you know that already too.
I have figured how to do what I wanted.
The intent was to return multiple values from a function with the minimal amount of "work".
I try to avoid passing return values as writable references (to avoid value mutation and assignment operators), so I wanted to do this by wrapping the objects to be returned in a struct.
I have succeeded at this before, so I was surprised that the code above didn't work.
This does work:
#include <iostream>
struct A {
bool x;
explicit A(bool x) : x(x) {
std::cout << "A constructed" << std::endl;
}
A(const A &other) : x(other.x) {
std::cout << "A copied" << std::endl;
}
A(A &&other) : x(other.x) {
std::cout << "A moved" << std::endl;
}
A &operator=(const A &other) {
std::cout << "A reassigned" << std::endl;
if (this != &other) {
x = other.x;
}
return *this;
}
};
struct B {
A a;
};
B foo() {
return B{A{true}};
}
int main() {
B b = foo();
std::cout << b.a.x << std::endl;
}
output:
A constructed
1
The key was to remove all the constructors of B. This enabled aggregate initialization, which seems to construct the field in-place. As a result, copying A is avoided. I am not sure if this is considered copy elision, technically.

RVO (Return Value Optimization) doesn't explain this mystery

Simple program:
#include <iostream>
using namespace::std;
class X {
public:
X() {
cout << "Default Constructor called\n";
i = 0;
}
X(int i) {
cout << "Parameterized Constructor called\n";
this->i = i;
}
X(const X& x) {
cout << "Copy Constructor called\n";
i = x.getI();
}
~X() {
cout << "Destructor called\n";
}
int getI() const {
return i;
}
X func() {
cout << "Entered func\n";
X x(2);
return x;
}
private:
int i;
};
int main() {
X x1;
X x2 = x1.func();
cout << "Returned from func\n";
}
It outputs the following:
Default Constructor called
Entered func
Parameterized Constructor called
Copy Constructor called
Destructor called
Returned from func
Destructor called
Destructor called
After the 'Returned from func' is printed, no constructor is called when creating the instance x2. I was actually expecting a copy constructor to be called when instantiating x2 as it would have been if we did something like X x2 = x1;
Now, I was told that this is a result of RVO. Is it true?
In wiki, RVO is defined as:
Return value optimization, or simply RVO, is a compiler optimization technique that involves eliminating the temporary object created to hold a function's return value.
But, I don't buy this for two reasons:
1.x2 here is not a temporary object.
2. If it were really to be the case, then the compiler would have been much better off implementing RVO when x was being returned from the function. That was a genuine case of a temporary object (during the return statement).
So, please explain why x2 was not instantiated after the function returned an object of X.
It's much easier to see what's happening if you get your functions to output more precise information. Consider:
#include <iostream>
struct X
{
X() : i_(0) { std::cout << "X(" << this << ")\n"; }
X(int i) : i_(i) { std::cout << "X(" << this << ", i " << i << ")\n"; }
X(const X& rhs) : i_(rhs.i_) { std::cout << "X(" << this << ", const X& "
<< &rhs << ")\n"; }
~X() { std::cout << "~X(" << this << ")\n"; }
X func() { std::cout << "X::func(this " << this << ")\n"; X x(2); return x; }
int i_;
};
int main()
{
X x1;
X x2 = x1.func();
std::cout << "x1 " << &x1 << ", x2 " << &x2 << '\n';
}
Output on ideone.com was:
X(0xbfd346e8)
X::func(this 0xbfd346e8)
X(0xbfd346ec, i 2)
x1 0xbfd346e8, x2 0xbfd346ec
~X(0xbfd346ec)
~X(0xbfd346e8)
That shows full RVO and elided copy-construction, which will be typical of most compilers at normal production optimisation levels (and quite possibly even lower levels).
I suggest you run the above code on your compiler with whatever flags you've been using, and the addresses displayed should make it clearer to you exactly which objects are involved in the various operations you've observed. As is, your comments...
Observe carefully. That is called after the 'func entered' is printed. That means that it got called from within the function, i.e. it was referring to the instance x, not x2.
...is flawed logic (with more attitude to boot "I'll just wait for someone who actually understands copy constructors to answer this."), which the better logging should help you realise. (Then hopefully you'll apologise to Praetorian.)

Compiler destructs an optimized-out object (never created)

I simplified my code down to the root of the trouble:
//==============================================================================
// PRE-DEFINITIONS
#define GIVE_ME_ODD_BEHAVIOUR true
//==============================================================================
// INCLUDES
#include <iostream>
//==============================================================================
// TYPES
//------------------------------------------------------------------------------
template<typename T> struct X {
T data;
X() : data(0)
{ std::cout << "X construction # " << this << std::endl; }
template<typename TT>
X(const X<TT> & other) : data(other.data)
{ std::cout << "X construction # " << this << " from " << &other << std::endl; }
~X()
{ std::cout << "X destruction # " << this << std::endl; }
template<typename TT>
void Copy(const X<TT> & other)
{ std::cout << "X copy # " << this << " from " << &other << std::endl; }
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
template<typename T>
X<double> XConversion(const X<T> & other)
{
#if GIVE_ME_ODD_BEHAVIOUR
return X<double>(other);
#else
X<double> d;
d.Copy(other);
return d;
#endif
}
//==============================================================================
// MAIN
int main()
{
X<double> d;
X<int> i;
std::cout << std::endl;
d = XConversion(i);
std::cout << std::endl;
d = XConversion(d); // !!!
std::cout << std::endl;
return 0;
}
which, with
GIVE_ME_ODD_BEHAVIOUR true
gives the output:
X construction # 0x23aa70
X construction # 0x23aa60
X construction # 0x23aa80 from 0x23aa60
X destruction # 0x23aa80
X destruction # 0x23aa90 // never created !!!
X destruction # 0x23aa60
X destruction # 0x23aa70
It seems similar to THIS problem but i do have the ctor defined. I see the copy elision optimisation point here but:
why the copiler destructs the object that it optimised out and thus never created?
how can I ensure that it does not happen ?
Additional info:
I tried gcc 4.8.1 and C++ Builder XE3, optimisation off, debug compilation, both with the same result.
I tried to remove the template-ness like
struct X {
double data
...
X(const X &)
...
};
but with the same result.
One way to solve this would be, e.g., to make the cctor private and to public the Copy method only. But this would prevent me (anybody) from even returning the object from a function...
Background:
I have a set of template classes that are mutually friends and convertible. The classes share the same data but manipulate them in different ways via partial specialisation. The instances may either do soft or raw copy of the data as needed. If they do the soft copy, ref counter is increased. With the extra deletion of the instance that was actually never created, the ref counter is decreased without its prior incrementation.
Seems the compiler generates a copy constructor for your case, maybe it does not like the one with the templates (if I recall correctly copy constructors are not (or may not be) template based ... might be wrong here though)... see: Copy constructor of template class
Adding the following code:
X(const X & other) : data(other.data)
{ std::cout << "X Copy construction # " << this << " from " << &other << " as " << typeid(X).name() << std::endl; }
template<typename TT>
X(const X<TT> & other) : data(other.data)
{ std::cout << "X construction # " << this << " from " << &other << " as " << typeid(TT).name() << std::endl; }
gives the following output:
X construction # 0x7fff3496c040
X construction # 0x7fff3496c038
X construction # 0x7fff3496c020 from 0x7fff3496c038 as i
X destruction # 0x7fff3496c020
X Copy construction # 0x7fff3496c018 from 0x7fff3496c040 as 1XIdE
X destruction # 0x7fff3496c018
X destruction # 0x7fff3496c038
X destruction # 0x7fff3496c040
so there is your missing object. (tested both with clang and g++, same behaviour)

Two-step copy elision to capture rvalue in constructor call as instance variable

I am trying to get an rvalue instance of this class:
#include <iostream>
#define msg(x) std::cout << x " constructor\n"
struct X {
int i;
X(int i) : i(i) {msg("X");}
X(const X& x) : i(x.i) {std::cout << "X copy\n";}
X(X&& x) {std::swap(i, x.i); std::cout << "X move\n";}
};
into instance variable x of this class:
struct A {
X x;
A(X x) : x(x) {msg("A");}
};
like so:
int main() {
A a(X(1));
std::cout << a.x.i << "\n\n";
}
without any copies or moves being made.
According to these references,
http://thbecker.net/articles/rvalue_references/section_01.html
http://www.slideshare.net/oliora/hot-11-2-new-style-arguments-passing
and many many posts on SO (so please read to the end before flagging as duplicate), I should rely on copy elision, whose conditions should be satisfied if I pass by value. Note that there are two copy elisions required, namely:
constructor call -> constructor local variable -> instance variable
as can be seen when turning copy elision off (compile with g++-4.8 -std=c++11 -fno-elide-constructors):
X constructor
X move
X copy
A constructor
1
So there is one move step and one copy step, which should both go away if I turn copy elision on (compile with g++-4.8 -std=c++11 -O3):
X constructor
X copy
A constructor
1
Bummer, the copy step remained!
Can I get any better with any other variation of std::move(), std::forward or passing as rvalue-reference?
struct B {
X x;
B(X x) : x(std::move(x)) {msg("B");}
};
struct C {
X x;
C(X x) : x(std::forward<X>(x)) {msg("C");}
};
struct D {
X x;
D(X&& x) : x(std::move(x)) {msg("D");}
};
int main() {
B b(X(2));
std::cout << b.x.i << "\n\n";
C c(X(3));
std::cout << c.x.i << "\n\n";
D d(X(4));
std::cout << d.x.i << "\n\n";
}
which produces the output:
X constructor
X move
B constructor
2
X constructor
X move
C constructor
3
X constructor
X move
D constructor
4
OK, I turned the copy into a move, but this is not satisfactory!
Next, I tried to make the instance variable x a reference X&:
struct E {
X& x;
E(X x) : x(x) {msg("E");}
};
int main() {
E e(X(5));
std::cout << e.x.i << "\n\n";
}
which produces:
X constructor
E constructor
1690870696
Bad idea! I got rid of the move but the rvalue instance that x was referencing to got destroyed under my seat, so the last line prints garbage instead of 5. Two notes:
g++-4.8 didn't warn me of anything, even with -pedantic -Wall -Wextra
The program prints 5 when compiled with -O0
So this bug may go unnoticed for quite a while!
So, is this a hopeless case? Well no:
struct F {
X& x;
F(X& x) : x(x) {msg("F");}
};
int main() {
X x(6);
F f(x);
std::cout << f.x.i << "\n";
}
prints:
X constructor
F constructor
6
Really? No fancy new C++11 features, no copy elision at the discretion of the compiler, just plain old FORTRAN66-style pass-by-reference does what I want and probably will perform best?
So here are my questions:
Is there any way one can get this to work for rvalues? Did I miss any features?
Is the lvalue-reference version really the best, or are there hidden costs in the X x(6) step?
Could there be any inconveniences introduced by x living on after the construction of f?
Could I pay a data locality penalty for using the lvalue reference to an external instance?
Without going into too much detail of your question, copy elision is basically used as much as possible. Here's a quick demo:
#include <iostream>
#include <utility>
struct X
{
int n_;
explicit X(int n) : n_(n) { std::cout << "Construct: " << n_ << "\n"; }
X(X const & rhs) : n_(rhs.n_) { std::cout << "X copy:" << n_ << "\n"; }
X(X && rhs) : n_(rhs.n_) { rhs.n_ = -1; std::cout << "X move:" << n_ << "\n"; }
~X() { std::cout << "Destroy: " << n_ << "\n"; }
};
struct A
{
explicit A(X x) : x_(std::move(x)) {};
X x_;
};
struct B
{
X x;
};
int main()
{
A a(X(12));
B b { X(24) };
}
This produces:
Construct: 12
X move:12
Destroy: -1
Construct: 24
Destroy: 24
Destroy: 12
The one move in x_(std::move(x)) is not elidable, since it doesn't involve a function return. But that's pretty good anyway. And notice how the aggregate b is indeed initialized "in-place".
Your example F shows that you're willing to expose the coupling of X to its ambient class. In that case, you could make a special constructor for A that constructs the X directly:
struct A
{
explicit A(X x) : x_(std::move(x)) {};
X x_;
// Better approach
struct direct_x_t {};
static direct_x_t direct_x;
// In our case, this suffices:
A(direct_x_t, int n) : x_(n) {}
// Generally, a template may be better: (TODO: add SFINAE control)
template <typename ...Args> A(direct_x_t, Args &&... args)
: x_(std::forward<Args>(args)...) {}
};
Usage:
A a(A::direct_x, 36);

How to pass by lambda in C++0x?

It seems the way to construct objects in C++0x avoiding copies/moves (particularly for large stack allocated objects) is "pass by lambda".
See the following code:
#include <iostream>
#define LAMBDA(x) [&] { return x; }
class A
{
public:
A() {};
A(const A&) { std::cout << "Copy "; }
A(A&&) { std::cout << "Move "; }
};
class B1
{
public:
B1(const A& a_) : a(a_) {}
B1(A&& a_) : a(std::move(a_)) {}
A a;
};
class B2
{
public:
B2(const A& a_) : a(a_) {}
B2(A&& a_) : a(std::move(a_)) {}
template <class LAMBDA_T>
B2(LAMBDA_T&& f, decltype(f())* dummy = 0) : a(f()) {}
A a;
};
int main()
{
A a;
std::cout << "B1 b11( a ): ";
B1 b11(a);
std::cout << std::endl;
std::cout << "B2 b12(LAMBDA(a)): ";
B2 b12(LAMBDA(a));
std::cout << std::endl;
std::cout << std::endl;
std::cout << "B1 b21( std::move(a) ): ";
B1 b21(std::move(a));
std::cout << std::endl;
std::cout << "B2 b22(LAMBDA(std::move(a))): ";
B2 b22(LAMBDA(std::move(a)));
std::cout << std::endl;
std::cout << std::endl;
std::cout << "B1 b31(( A() )): ";
B1 b31((A()));
std::cout << std::endl;
std::cout << "B2 b32((LAMBDA(A()))): ";
B2 b32((LAMBDA(A())));
std::cout << std::endl;
std::cout << std::endl;
}
Which outputs the following:
B1 b11( a ): Copy
B2 b12(LAMBDA(a)): Copy
B1 b21( std::move(a) ): Move
B2 b22(LAMBDA(std::move(a))): Move
B1 b31(( A() )): Move
B2 b32((LAMBDA(A()))):
Note the "pass by lambda" removes the move in the case where the parameter is a what I believe is called a "prvalue".
Note that it seems the "pass by lambda" approach only helps when the parameter is a "prvalue", but it doesn't seem to hurt in other cases.
Is there anyway to get functions to accept "pass by lambda" parameters in C++0x, that is nicer than the client having to wrap their parameters in lambda functions themselves? (other than defining a proxy macro that calls the function).
If you're okay with a templated constructor, you might as well use perfect forwarding instead of the obfuscation with lambdas.
class super_expensive_type {
public:
struct token_t {} static constexpr token = token_t {};
super_expensive_type(token_t);
}
constexpr super_expensive_type::token_t super_expensive_type::token;
class user {
public:
template<typename... Args>
explicit
user(Args&&... args)
: member { std::forward<Args>(args)... }
{}
private:
super_expensive_type member;
};
// ...
// only one construction here
user { super_expensive_type::token };
super_expensive_type moved_from = ...;
// one move
user { std::move(moved_from) };
super_expensive_type copied_from = ...;
// one copy
user { copied_from };
Using lambdas can't be better than this because the result from the expression in the lambda body has to be returned.
There's a fundamental problem with what you're doing. You cannot magic an object into existence. The variable must be:
Default constructed
Copy constructed
Move constructed
Constructed with a different constructor.
4 is off the table, since you only defined the first three. Your copy and move constructors both print things. Therefore, the only conclusion one can draw is that, if nothing is printed, the object is being default constructed. IE: filled with nothing.
In short, your Lambda-based transfer mechanism doesn't seem to be transferring anything at all.
After further analysis, I see what's happening. Your lambda isn't actually taking a value by reference; it's constructing a value. If you expand the macro, what you get is this:
B2 b32(([&] {return A()}));
It constructs a temporary; it doesn't actually take anything by reference. So I'm not sure how you can consider this "passing" anything. All you're doing is making a function that constructs an object. You could just as easily pass the arguments for B2::a's constructor to the constructor of B2 and have it use them to create the object, and it would give you the same effect.
You're not passing a value. You're making a function that will always create the exact same object. That's not very useful.