For some reason my temporary local object is always copy-constructed/destroyed when added to a vector which is causing problems due to nested std::reference_wrapper which are getting invalid because of the copy-construction and destruction afterwards (std::reference_wrapper targets are inside the object which gets destroyed -> so they are invalid in the copy-constructed object, if the source object is destroyed). But if possible I want to avoid the additional copying / destroying completely - that seems impossible, because whatever I tried always it wants to invoke the copy constructor (even using std::vector::emplace_back).
Considering this simple example (for easier understanding without std::reference_wrapper involved), it always tries to invoke the copy-constructor - I don't get why.
#include <vector>
class A{
public:
A(int a) : a(a){ }
int getInt() const{ return a; }
A(const A&) = delete; /* to deny copy-construction */
private:
int a;
};
int main(int argc, char* argv[]){
std::vector<A> vec;
vec.emplace_back(3); /* tries to call copy constructor */
vec.push_back(A(3)); /* tries to call copy constructor */
vec.push_back(std::move(A(3))); /* tries to call copy constructor */
return 0;
}
Any ideas what I'm missing here?
Per the Visual Studio 2013 documentation, emphasis mine:
"Rvalue references v3.0" adds new rules to automatically generate move constructors and move assignment operators under certain conditions. However, this is not implemented in Visual C++ in Visual Studio 2013, due to time and resource constraints.
Visual Studio 2013 is specified as using Rvalue references v2.1.
Note: As T.C. notes in the comments there is also an issue with explicitly disabling the copy constructor in your example. Per cppreference.com.
If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
there are no user-declared copy constructors
there are no user-declared copy assignment operators
there are no user-declared move assignment operators
there are no user-declared destructors
(until C++14) the implicitly-declared move constructor is not defined as deleted due to conditions detailed in the next section
then the compiler will declare a move constructor as an inline public member of its class with the signature T::T(T&&).
A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.
Which means your example code also prevents auto generation of the move constructor (i.e., it has a user-declared copy constructor).
You need to explicitly declare your move constructor and/or move assignment operator. The following works for your example.
class A
{
public:
A(int a) : a(a) {}
A(const A&) = delete;
A(A&& other) : a(other.a) {}
int getInt() const { return a; }
private:
int a;
};
int main(int argc, char* argv[])
{
std::vector<A> vec;
vec.emplace_back(3);
vec.push_back(A(3));
vec.push_back(std::move(A(3)));
return 0;
}
Related
The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.
The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.
Quoted from C++ Primer
if we explicitly ask the compiler to generate a move operation by
using = default, and the compiler is unable to move all the
members, then the move operation will be defined as deleted
the move constructor is defined as deleted if the
class has a member that defines its own copy constructor but does not also
define a move constructor, or if the class has a member that doesn’t define its
own copy operations and for which the compiler is unable to synthesize a move
constructor
Some code seems to violate this rule:
#include <utility>
#include <iostream>
struct X {
X() = default;
X(const X&) { std::cout << "X(const X&)" << std::endl; }
int i;
};
struct hasX {
hasX() = default;
hasX(const hasX &) = delete;
hasX(hasX &&) = default;
X mem;
};
int main()
{
hasX hx, hx2 = std::move(hx); //output is X(const X&)
}
X doesn't define move constructor and compiler can't synthesize one for it.
According to the rule above, hasX's move constructor is deleted.
However, because hasX's copy constructor is deleted, hx2 = std::move(hx) must call move constructor to output "X(const X&)", which shows hasX's move constructor is defined and it use X's copy constructor to "move". This seems to violate the rule above.
So, is't defined in C++ standard or just a compiler implementation?
Compiler I tested: VS2015 and a online compiler
Thank you for help!
Your book seems to be wrong. Copying is a valid move operation so as long as the member has a copy constructor in the form of const type&, so it can bind to rvalues, the move operation will fall back to a copy.
In your example
hasX(hasX &&) = default;
Can be replaced with
hasX(hasX &&rhs) : mem(std::move(rhs.mem)) {}
as that is what the default does and it will compile just fine.
While I was learning std::move, I found a strange issue.
If I add only a destructor that do nothing to a perfect program, I will get a compile error.
#include <iostream>
using namespace std;
class M {
public:
int database = 0;
M &operator=(M &&other) {
this->database = other.database;
other.database = 0;
return *this;
}
M(M &&other) { *this = std::move(other); }
M(M &m) = default;
M() = default;
~M() { /* free db */ }
};
class B {
public:
M shouldMove;
//~B(){} //<--- ## Adding this line will cause compile error. ##
};
int main() {
B b;
B b2 = std::move(b); //## error at this line if the above line is added
return 0;
}
Live code: https://ideone.com/UTR9ob
The error is invalid initialization of non-const reference of type 'B&' from an rvalue of type 'std::remove_reference<B&>::type {aka B}'.
Question:
(1) Which rules of C++ syntax enforce that? In other words, what does the error mean?
(2) If I want to add destructor that do almost nothing (e.g. only print debug log) to B, do I really have to follow the rule-of-five instead? If no, how to make it compile? Following the rule of five just because of that is too tedious and dirty in my opinion.
I think the rule of zero is just a good practice.
However, from this example, it seems to me that it is a hard rule that if violated, I will get compile error.
The implicitly-declared move constructor is only present if a class does not have a user-declared destructor. Therefore the answer to 2. is YES.
The answer to 1. is that this is hard rule and can be found in 12.8, paragraph 9 of the standard:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator,
X does not have a user-declared destructor, and
the move constructor would not be implicitly defined as deleted.
[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that
otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]
The best way of getting this to run,is by using something like a smart pointer, i.e., a base class or member that does define all five special members (and very little else) so that you don't have to. In this case, an integer handle equivalent to std::unique_pointer should work well. However, keep in mind that databases, like files, can have errors while closing, so standard non-throwing destructor semantics don't cover all cases.
I have some problems understanding when and if the move constructor or move assignment operator are invoked, in particular in the context of a class with constant data member.
Consider the class
template<typename T> class A {
const*T const P ; // constant data member
explicit A(const*T p) : P(p) { std::cerr<<" ctor: P="<<P<<'\n'; }
void test() const { std::cerr" test: P="<<P<<'\n'; }
// move and copy constructors and assignment operators here
};
and the test program
class B {
int X[100];
A<B> get_a() const { return A<B>(this); }
};
int main() {
B b;
A<B> a = b.get_a(); // which operator/ctor is used for '=' here?
a.test();
}
then the results for compilation are different depending on the definitions provided for the move constructor and move assignment operator in class A<>, but also on compiler.
1 without any further declaration in class A<> (as above), both g++ (4.7.0) and icpc (13.0.1) compile fine (with option -std=c++11) and produce the expected output
ctor: P=0x7fffffffd480
test: P=0x7fffffffd480
2 if I declare
A&A::operator=(A&&) = delete;
A&A::operator=(const A&) = delete;
(which seems sensible in view of the constant data member which must be initialiser-list initialised), but don't provide any further ctor, compilation fails with g++ but is okay wich icpc. If in addition I define either (or both) of
A::A(A&&) = default;
A::A(const A&) = default;
both compilers are happy. However, g++ is not happy with the combination
A::A(A&&) = delete;
A::A(const A&) = default;
while icpc is happy.
3 If I play the same game as in 2, except that A::A(A&&) = default; is replaced by
A::A(A&&a) : P(a.P) { std::cerr<<" move ctor: P="<<P<<'\n'; } // never called?
(and equivalent for A::A(const A&)), the results are completely identical, in particular no output is generated from these explicit move and copy ctors.
So which operator is used for = in main()? (and why is no output produced in the last test?)
And why is this operation allowed here at all, given that A<> has a constant data member (the results are identical if I replace the member const*T const P; with const T&R)?
Finally, in case of the different behaviours of g++ and icpc, which, if any, is correct?
A<B> a = b.get_a();
is not an assignment, but an initialization of a from an rvalue. This syntax should fail under C++0x if
the move constructor is deleted,
the move constructor is declared explicit,
the copy constructor is deleted and no move constructor is defined,
no move constructor is defined and, at the same time the move-assignment is defined or deleted.
Declaration or deletion of the copy assignment operator should not have any influence.
Correction: Different to the copy constructor (which is synthesized even if a user-defined copy assignment operator is provided), the compiler does not synthesize a move constructor if a user-defined move assignment is defined. Hence, the above list should be amended by 4 (which I have done now).
Hence, in my opinion,
in [1] in the question, both compilers behave correctly,
in [2]/1, gcc behaves correctly (move assignment prevents generation of move constructor), icpc is wrong,
in [2]/2, both compilers are correct,
in [2]/3, gcc is correct, since the move constructor is explicitly deleted; icpc is wrong,
[3] is a mystery to me. Are you sure you're correct?