Lets say we have the following code:
#include <iostream>
#include <string>
struct A
{
A() {}
A(const A&) { std::cout << "Copy" << std::endl; }
A(A&&) { std::cout << "Move" << std::endl; }
std::string s;
};
struct B
{
A a;
};
int main()
{
B{A()};
}
Here, I believe struct A is not an aggregate, as it has both non-trivial constructors and also a std::string member which I presume is not an aggregate. This presumably means that B is also not an aggregate.
Yet I can aggregate initialize B. In addition this can be done without either the copy nor move constructor being called (e.g. C++0x GCC 4.5.1 on ideone).
This behavior seems like a useful optimization, particularly for composing together large stack types that don't have cheap moves.
My question is: When is this sort of aggregate initialization valid under C++0x?
Edit + follow up question:
DeadMG below answered with the following:
That's not aggregate initialization at all, it's uniform initialization, which basically in this case means calling the constructor, and the no copy or move is probably done by RVO and NRVO.
Note that when I change B to the following:
struct B
{
A a;
B(const A& a_) : a(a_) {}
B(A&& a_) : a(std::move(a_)) {}
};
A move is performed.
So if this is just uniform initialization and just calling the constructor and doing nothing special, then how do I write a constructor that allows the move to be elided?
Or is GCC just not eliding the move here when it is valid to do so, and if so, is there a compiler and optimization setting that will elide the move?
According to the new standard, clause 8.5.1 (Aggretates), a sufficiently simple type (e.g. no user-defined constructors) qualifies as an aggregate. For such an aggregate Foo, writing Foo x{a, b, ... }; will construct the members from the list items.
Simple example:
struct A
{
std::unordered_map<int, int> a;
std::string b;
std::array<int,4> c;
MyClass d; // Only constructor is MyClass(int, int)
};
// Usage:
A x{{{1,-1}, {12, -2}}, "meow", {1,2,3,4}, MyClass(4,4)};
// Alternative:
A x{{{1,-1}, {12, -2}}, "meow", {1,2,3,4}, {4,4}};
The object x is constructed with all the relevant constructors executed in place. No maps or strings or MyClasses ever get copied or moved around. Note that both variants at at the bottom do the same thing. You can even make MyClass's copy and move constructors private if you like.
That's not aggregate initialization at all, it's uniform initialization, which basically in this case means calling the constructor, and the no copy or move is probably done by RVO and NRVO.
Related
I have an object which is non-copyable and which requires an argument to its contructor:
class Foo
{
public:
Foo() = delete;
Foo(const Foo &) = delete;
Foo(int x);
private:
int member;
};
Foo::Foo(int x) : member(x)
{
}
I have another class which contains as a member an array of such objects:
class Bar
{
public:
Bar(int a);
private:
Foo members[4];
};
Suppose that my object Foo is really far too big and its constructor far too complicated for a temporary or duplicate copy to ever exist or the constructor to ever be called more than once for each item of the array.
How do I write the constructor for the containing class to pass the arguments to the items in the member array?
I have tried:
Bar::Bar(int a) : members { a, a+1, a+2, a+3 }
{
}
g++ says "use of deleted function Foo::Foo(const Foo&)".
[Edit] I have also tried:
Bar::Bar(int a) : members { {a}, {a+1}, {a+2}, {a+3} }
{
}
as suggested by Yksisarvinen. This also says "use of deleted function Foo::Foo(const Foo&)", but only if I declare a destructor for Foo. Without a destructor this compiles correctly. How do I get it to compile for a class which has a destructor?
Three options here, depending on what you actually want to do:
1. Use C++17
C++17 has guaranteed copy elision, which will solve the problem.
2. Provide a move constructor
Compiler chooses copy constructor, because by declaring your own copy constructor you prevented possibility to generate a move constructor. Depending on your actual class, move constructor may or may not be viable.
Foo(Foo &&) = default; //or implement "stealing" of the resource here
Note that by the rule of five you should also provide copy/move assignment operators and destructor (defaulted or not).
3. Wrap each array element in braces
If neither of above options is valid, the workaround is to simply wrap each of the array elements in its own set of braces
Bar::Bar(int a) : members { {a}, {a+1}, {a+2}, {a+3} }
In general, your previous option relied on conversion from int to Foo. Once the compiler does the conversion, it has to copy (or move, but you excluded that) the converted object to array, and that triggers an error. By wrapping them in braces, the elements are initialized as Foo objects (and not as int to be converted to Foo), and no move/copy is needed.
cppreference says:
The underlying array is a temporary array of type const T[N], in which
each element is copy-initialized (except that narrowing
conversions are invalid) from the corresponding element of the
original initializer list. The lifetime of the underlying array is the
same as any other temporary object, except that initializing an
initializer_list object from the array extends the lifetime of the
array exactly like binding a reference to a temporary (with the same
exceptions, such as for initializing a non-static class member). The
underlying array may be allocated in read-only memory.
What's the reasoning behind this decision? Why is moving not ok?
What about copy-ellision?
struct A { A(const A&){ std::cout << "Oh no, a copy!\n"; } };
struct B { B(std::initializer_list<A> il); };
int main()
{
B b{ A{} };
return 0;
}
My compiler ellides the copy. But are these copies guaranteed to be ellided?
"Copy initialization" in C++ doesn't mean things will necessarily be copied. It's just a formal name for the constraints under which initialization will occur. For instance, when copy initializing, explicit c'tors are not candidates. So the following code will be ill-formed
#include <iostream>
#include <initializer_list>
struct A {
explicit A() = default;
A(const A&){ std::cout << "Oh no, a copy!\n"; }
};
struct B { B(std::initializer_list<A> il); };
int main()
{
B b{ {} };
return 0;
}
The single member in the list needs to be copy-initialized from {}, which entails calling the default c'tor. However, since the c'tor is marked explicit, this initialization cannot happen.
Copy elision is certainly possible pre-C++17, and is mandatory in C++17 onward in certain contexts. In your example, under a C++17 compiler, since you provide an initializer that is a prvalue (a pure rvalue, not an object), the initialization rules of C++ mandate that the target is initialized directly, without intermediate objects created. Even though the context is called "copy initialization", there are no superfluous objects.
#include <iostream>
#include <string>
#include <array>
class C {
private:
std::string a;
std::string b;
std::string c;
public:
C(std::string a_, std::string b_, std::string c_) : a{a_},b{b_},c{c_} {}
~C(){};
C(const C&) =delete;
C(const C&&) =delete;
const C& operator=(const C&) =delete;
const C& operator=(const C&&) =delete;
};
std::array<C,2> array = {C("","",""),C("","","")};
int main()
{}
this won't compile (Android Studio with NDK and clang) with a "call to deleted constructor of c" error. I know that I can e.g. use a std::vector and emplace_back() to construct an element directly inside the container, but in my code I want to only use fixed-sized containers and non-copyable/moveable objects for optimization. I'm probably missing sth basic here, but isn't there a way to initialize the std::array without first having to construct the individual elements and then copy them there?
You can use brace enclosed initializers instead of temporary c objects:
std::array<c,2> array = {{{"",""},{"",""}}};
or
std::array<c,2> array{{{"",""},{"",""}}};
It would become possible since C++17, from that for some specified cases copy elision is guaranteed.
Under the following circumstances, the compilers are required to omit
the copy- and move- constructors of class objects even if copy/move
constructor and the destructor have observable side-effects:
In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the
class of the destination, the initializer expression is used to
initialize the destination object:
T x = T(T(T())); // only one call to default constructor of T, to initialize x
And for these cases, copy/move constructor is not required to be accessible.
When copy-elision takes place (until C++17) In those cases where
copy-elision is not guaranteed, if it takes place (since C++17) and
the copy-/move-constructor is not called, it must be present and
accessible (as if no optimization happened at all), otherwise the
program is ill-formed.
LIVE
If I have a struct in which I did not provide any copy and move constructor:
struct MyStruct {
MyStruct() { // this is the only function
...
}
...
};
then if I do the following:
std::vector<MyStruct> vec;
...
vec.push_back(MyStruct());
instead of using std::move() like the followings:
vec.push_back(std::move(MyStruct()));
Will c++11 smartly do the move for my temporary variable? Or, how can I make sure it is a move instead of a copy?
In C++11 std::vector::push_back will use a move constructor if passed an rvalue (and a move constructor exists for the type), but you should also consider using std::vector::emplace_back in such situations; std::vector::emplace_back will construct the object in place rather than moving it.
Will c++11 smartly do the move for my temporary variable? Or, how can I make sure it is a move instead of a copy?
It depends. This
vec.push_back(MyStruct());
will bind to
std::vector<MyStruct>::push_back(MyStruct&&);
but whether the rvalue passed is moved or copied depends fully on whether MyStruct has a move copy constructor (likewise for move assignment).
It will make absolutely no difference if you call
vec.push_back(std::move(MyStruct()));
because MyStruct() is already an rvalue.
So it really depends on the details of MyStruct. There is simply not enough information in your question to know if your class has move constructor.
These are the conditions that must be met for a class to have an implicitly generated move constructor:
no user-declared copy constructors
no user-declared copy assignment operators
no user-declared move assignment operators
no user-declared destructors
Of course, you can always provide your own if any of these conditions are not met:
MyStruct(MyStruct&&) = default;
Because the MyStruct() will create an rvalue the T && overload will be called.
It's actually very easy to verify (demo):
#include <iostream>
struct A{ int x; };
void foo(A &&x){ std::cout<<"&&" <<std::endl; }
void foo(A &x){ std::cout<<"&" <<std::endl; }
int main() {
foo(A()); // prints &&
A a;
foo(a); // prints &
return 0;
}
To clarify: I didn't mention anything about move constructor because one can have an explicitly deleted move constructor and still the T && will be invoked.
E.g (demo):
#include <iostream>
struct A{ int x; A() = default; A(const A& ) = default; A(A&&) = delete; };
/* ^
no move ctor */
void foo(A &&x){ std::cout<<"&&" <<std::endl; }
void foo(A &x){ std::cout<<"&" <<std::endl; }
int main() {
foo(A()); //still prints &&
A a;
foo(a);
return 0;
}
As I said before this is because it's an rvalue...
Yes, it is going to use push_back( T&& value ), and move the value.
If the type is movable then it definitely will be. In general standard-complying compiler should always choose move semantics to copy semantics if such are available.
When I read about copy initializing vs direct initializing here. copy constructor should call in copy initializing. why here copy constructor not calling?
#include <iostream>
using namespace std;
class A{};
class B{
public:
B(const A &a){cout << "B construct from A" << endl;}
B(const B &b){cout << "B copy constructor" << endl;}
};
int main(){
A a;
B b = a;
return 0;
}
This is Copy ElisionRef 1:.
Copy constructor calls while generating temporaries might be optimized by the compiler by creating objects inline and it is explicitly allowed by the C++ Standard.
This is nicely demonstrated in the standard with an example as well:
C++03 Standard 12.2 Temporary objects [class.temporary]
Para 2:
[Example:
class X {
// ...
public:
// ...
X(int);
X(const X&);
˜X();
};
X f(X);
void g()
{
X a(1);
X b = f(X(2));
a = f(a);
}
Here, an implementation might use a temporary in which to construct X(2) before passing it to f() using X’s copy-constructor; alternatively, X(2) might be constructed in the space used to hold the argument. Also, a temporary might be used to hold the result of f(X(2)) before copying it to `b usingX’s copyconstructor; alternatively,f()’s result might be constructed in b. On the other hand, the expressiona=f(a)requires a temporary for either the argument a or the result off(a)to avoid undesired aliasing ofa`. ]
Ref 1:
C++03 12.8 Copying class objects [class.copy]
Para 12:
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects.....
Copy initialization is still subject to copy elision, and I'm guessing that's what's happening. Theoretically, a temporary B is constructed from a and the the copy constructor is used to create b from the temporary. In practice, the copy can be optimized out.
To test this, you can make the copy constructor private:
class B{
public:
B(const A &a){cout << "B construct from A" << endl;}
private:
B(const B &b){cout << "B copy constructor" << endl;}
};
and get a compilation error. This means the compiler expects the copy constructor to be accessible, but is not required to call it.
Copy elision is the only case where observed behavior can be altered.