please look at the following example code:
#include <iostream>
struct Foo {
Foo() { std::cout << "Default!\n"; }
Foo(const Foo& foo) { std::cout << "Copy!\n"; }
Foo(Foo&& foo) { std::cout << "Move!\n"; }
};
struct Bar {
Foo foo;
Bar() {}
Bar(Bar &that) : foo(that.foo) {}
Bar(Bar &&that) : foo(std::move(that.foo)) {}
};
Bar f() {
Bar bar;
return bar;
}
int main() {
Bar bar(f());
}
I'am expecting that the output of this code should be:
Default!
Move!
but what I get is:
Default!
Copy!
I can't see any reason why the copy constructor is called instead of the move constructor. If I put the keyword const in front of Bar &that in the declaration of the copy constructor of struct Bar, I've got the right result. I know that it is better for many cases to take a const lvalue reference rather than just an lvalue reference for copy constructors, but I just want to know the reason why this happened.
Why in this example Bar & has been preferred over Bar && even though the return value of f() should be considered as a prvalue? Why the keyword const solves the problem? Does const really solve the problem? Does it have something to do with RVO (Return Value Optimization)? Or, is this just a compiler bug?
I've tested this example on Visual C++ November 2012 CTP.
I've found a similar issue here:
Copy constructor is being called instead of the move constructor
But I still can't understand why.
Can anyone help me?
It's just the usual non-compliance of Visual C++ allowing binding a non-const lvalue reference to a temporary. It violates the language rules, but it went too long before being caught, so now there's code that depends on the bug that would break if it were properly fixed.
That behavior mistakenly allowing the non-const copy constructor to be used, combined with incomplete rvalue reference support in that Visual C++ version, evidently results in the wrong overload being selected.
If you want to use C++11/C++14 features, you'd best stay on top of the latest Visual C++ version.
Wow, when I compile this with...
Visual Studio in Debug I see "Default! Copy!".
Visual Studio in Release I see "Default!".
If you change Bar(Bar &that) to Bar(const Bar &that) then "Default! Move!"
Shockingly, if you switch the order of Bar(Bar &that) with Bar(Bar &&that) (so that the move ctor is defined first) then you'll actually see "Default! Move!"
Your question is probably answered here.
A temporary object cannot bind to a non-const reference. The copy constructor must take a reference to a const object to be able to make copies of temporary objects.
The other thing is that temporary objects shall not be modifiable as they are to be destroyed anytime soon. Holding a reference to temporaries leads to potential modification on nonexistent objects out of carelessness.
Related
I recently read about a std::unique_ptr as a #property in objective c and the suggestion to store a unique_ptr in ObjC as a property is as following:
-(void) setPtr:(std::unique_ptr<MyClass>)ptr {
_ptr = std::move(ptr);
}
My question is in ObjC, does the parameter get copied in this case? Because if that happens, unique_ptr shall never be declared as a property right?
My question is in ObjC, does the parameter get copied in this case?
That depends. Let me introduce a custom class where all copy operations are removed to better demonstrate possible outcomes under different circumstances:
struct MyClass {
MyClass() {
std::cout << "Default constructor" << std::endl;
}
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
MyClass(MyClass&&) {
std::cout << "Move Constructor" << std::endl;
}
MyClass& operator=(MyClass&&) {
std::cout << "Move Assignment" << std::endl;
return *this;
}
};
And change the parameter signature of your method accordingly:
- (void)setInst:(MyClass)inst {
_inst = std::move(inst);
}
Initialise the parameter with a temporary
Assuming the method in the sample belongs to a class named TDWObject the following code will compile just fine:
[[TDWObject new] setInst:MyClass{}];
Under C++17, you will find the default constructor and the move assignment called:
Default constructor
Move Assignment
The default constructor is called for the temporary, and thanks to guaranteed copy elision, neither copy nor move constructor is needed to initialise the parameter inst of the method with the temporary. The move assignemnt is straightforward - it happen when assigning result of std::move(inst) operation. If you use C++11 or C++14, standard doesn't guarantee copy elision, but clang will do it anyway. Some compilers use move-semantic instead, but overall for a temporary this code should work just fine.
Initialise the parameter with a moved named variable
Another option is to cast any named variable to an rvalue, and it will still allow to initialise the parameter without any issues:
MyClass inst;
[[TDWObject new] setInst:std::move(inst)];
The difference in this case, is that the function parameter will actually call the move constructor without elision optimisation:
Default constructor
Move Constructor
Move Assignment
Initialise the parameter with a named variable
And here is the broken scenario:
TDW::MyClass inst;
[self setInst:inst];
This will not work of course, because the parameter needs to call the copy constructor, which is marked deleted. The good thing about it, this code will never compile, and you will spot the problem straight away.
Considering alternatives
First of all I don't really think that Objective-C properties are compatible with non-copyable C++ classes and decided to give my own answer to the question linked, which you can review here.
I would not expect that code to compile as ptr is being passed by value there.
Better would be:
-(void) setPtr:(std::unique_ptr<MyClass>) &&ptr {
_ptr = std::move(ptr);
}
Edit: Thinking about it, that might not compile either. I don't know if Objective_C understands passing parameters by reference, rvalue or otherwise. But if it doesn't, this should work:
-(void) setPtr:(std::unique_ptr<MyClass>) *ptr {
_ptr = std::move(*ptr);
}
I have such class in A.h :
class A {
public:
A(int *object) {
std::cout << "convert";
}
A(A &object) {
std::cout << "copy";
}
};
and in main.cpp
A a = new int;
then, when I'm trying to compile it i get
invalid initialization of non-const reference of type ‘A&’ from an
rvalue of type ‘A’
but when i add const to copy-constructor like that:
A(const A &object) {
std::cout << "copy";
}
code compiles and "convert" is called. It works when i remove copy constructor, too. Why such thing happen? I though this example has nothing to do with copying constructor as we do not use instance of class A to create another.
Because the way your code works is following (pre-C++17):
A a(A(new int));
Note, copy constructor of A is called with a temporary object crated as a result of A(new int). And you can't bind a non-const lvalue reference to a temporary.
Just a note, in C++17 this code will compile due to guaranteed copy-elision (in case of C++17, this code is semantically equivalent to A a(new int). You also won't see copy printed (because it will never be called due to copy elision)
A a = new int; is copy initialization. It takes what is on the right hand side and uses it to initialize what is on the left hand side as a copy of it. since new int isn't an A the compiler makes a temporary one calling the converting constructor. A temporary can't be bound to a non const reference so you get an error. When you add the const you can now bind the temporary to it and it will work. Removing the copy constructor also works as the compiler will provide one for you and the one it does takes in a const reference,
In the following code I want to move-construct an object that has no move-constructor available:
class SomeClass{
public:
SomeClass() = default;
SomeClass(const SomeClass&) = default;
SomeClass( SomeClass&&) = delete;
};
SomeClass& getObject(){
return some_obj;
};
//...
SomeClass obj = std::move( getObject());
The compiler gives an error: "use of deleted function". This is all good.
On the other hand, if it has a move-constructor but getObject() returns a const object, then the copy constructor will be called instead, even though I'm trying to move it with std::move.
Is it possible to make the compiler give a warning / error that std::move won't have any effect since the object can't be moved?
class SomeClass{
public:
SomeClass() = default;
SomeClass(const SomeClass&) = default;
SomeClass( SomeClass&&) = default;
};
const SomeClass& getObject(){
return some_obj;
};
//...
SomeClass obj = std::move( getObject());
If your concern is just knowing for sure that you're getting the best possible performance, most of the time you should probably just trust the compiler. In fact, you should almost never need to use std::move() at all. In your example above, for example, it's not having an effect. Modern compilers can work out when a move should happen.
If you have classes that should always be moved and never copied, then delete their copy constructors.
But maybe you're writing a template function that is going to have terrible performance if you pass it a class without a move constructor, or you're in some other situation I haven't thought of. In that case, std::is_move_constructible is what you probably want. Try this:
#include <type_traits>
#include <boost/serialization/static_warning.hpp>
template<class T>
T &&move_or_warn(T &t)
{
BOOST_STATIC_WARNING(std::is_move_constructible<T>::value);
return std::move(t);
}
Now if you do SomeClass obj = std::move_or_warn( getObject());, you should get a compiler warning if the object can't be moved. (Although I'd probably use the normal std::move and call std::is_move_constructible seperately.)
Unfortunately, C++ doesn't (yet) have a standard way to produce the sort of programmer-specified warning you're looking for, which is why I had to use boost. Take a look here for more discussion about generating warnings.
The problem comes from const rvalue. Such arguments match const T& better than T&&. If you really want to ban such arguments, you can add an overloaded move constructor:
SomeClass(const SomeClass&&) = delete;
Note: What you are trying is to ban such arguments, rather to ban the move behavior. Because we are usually not able to "steal" resource from a const object even if it is an rvalue, it is reasonable to call copy constructor instead of move constructor. If this is an XY problem, you should consider if it is really intended to ban such arguments.
Is it possible to make the compiler give a warning / error that std::move won't have any effect since the object can't be moved?
It is not exactly true that std::move won't have any effect. The following code (try it on wandbox):
void foo(const SomeClass&) {
std::cout << "calling foo(const SomeClass&)" << std::endl;
}
void foo(const SomeClass&&) {
std::cout << "calling foo(const SomeClass&&)" << std::endl;
}
int main() {
foo(getObject());
foo(std::move(getObject()));
}
will output
calling foo(const SomeClass&)
calling foo(const SomeClass&&)
even though your object has a deleted move constructor.
The reason is that std::move doesn't by itself "move" anything. It just does a simple cast (C++17 N4659 draft, 23.2.5 Forward/move helpers):
template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;
Returns: static_cast<remove_reference_t<T>&&>(t)
This is why the compiled doesn't give a warning - everything is perfectly legal, the cast you are doing has nothing to do with the deleted move constructor, and overload resolution selects the copy constructor as the best match.
Of course, you can define your own move with different semantics than std::move if you really need such semantics (like the one in matthewscottgordon's answer).
This question already has answers here:
What's the exact semantics of deleted member functions in C++11?
(2 answers)
Closed 8 years ago.
After doing a bit of research, I see that C++11 has a defect with allocators that require the type to be movable/copyable. I'm sure that this is the cause of this problem, however I am confused about the behavior between deleted and not declared move semantics.
I have the following code which fails to compile on both MSVC12 and Clang:
#include <vector>
class Copyable
{
public:
Copyable() = default;
Copyable(Copyable const& other)
: m_int(other.m_int)
{}
Copyable& operator= (Copyable const& other)
{
m_int = other.m_int;
return *this;
}
Copyable(Copyable&&) = delete;
Copyable& operator= (Copyable&&) = delete;
private:
int m_int = 100;
};
int main()
{
std::vector<Copyable> objects;
objects.push_back(Copyable{});
}
This fails to compile on MSVC with:
xmemory0(600): error C2280: 'Copyable::Copyable(Copyable &&)' : attempting to reference a deleted function
And on Clang (live sample):
new_allocator.h:120:23: error: call to deleted constructor of 'Copyable'
In both cases, when I remove the explicitly deleted move construct/assign methods, the code compiles. AFAIK when you declare copy assign/construct methods, the compiler does not implicitly declare the corresponding move members. So they should still be effectively deleted, right? Why does the code compile when I remove the explicit deletion of move construct/assign?
What is a good workaround for this C++11 defect in general? I do not want my objects to be movable (but they are copyable).
Deleting a function is not the same as not declaring it.
A deleted function is declared and participates in overload resolution, but if you attempt to call it an error is produced.
If you fail to declare your move constructor, the compiler will not create one as you created a copy constructor. Overload resolution on an rvalue will find your copy constructor, which is probably what you want.
What you said with your foo(foo&&)=delete was "if anyone tries to move construct this object, generate an error".
I can illustrate the difference here:
void do_stuff( int x ) { std::cout << x << "\n"; }
void do_stuff( double ) = delete;
void do_stuff2( int x ) { std::cout << x << "\n"; }
//void do_stuff2( double ) = delete;
int main() {
do_stuff(3); // works
//do_stuff(3.14); // fails to compile
do_stuff2(3); // works
do_stuff2(3.14); // works, calls do_stuff2(int)
}
the only part with your above problem that makes this a bit more confusing is that special member functions are automatically created or not based off slightly arcane rules.
Copyable(Copyable&&) = delete;
Copyable& operator= (Copyable&&) = delete;
Unless you are an expert in move semantics (and I mean, really knowledgeable), never delete the special move members. It won't do what you want. If you review someone else's code that has done this, call it out. The explanation has to be really solid, and not "because I don't want the type to move."
Just don't do it.
The proper way to do what you want is to simply declare/define your copy members. The move members will be implicitly inhibited (not deleted, but actually not there). Just write C++98/03.
For more details, see this answer.
i was doing some tests of the move semantics and I tried this :
class A{
public:
A(){printf("A CTOR\n");}
A(A&) {printf("A CTOR by copy\n");}
A(A&&){printf("A CTOR by universal reverence\n");}
};
A&& create(){
A v;
return std::move(v);
}
auto x{ create() };//this does not compile
float d[]{1.2,1.3,5,6};//this does compile
I get the following error:
error C3086: cannot find 'std::initializer_list': you need to #include <initializer_list>
I don't understand as the initializer list feature has been added to VC11 with CTP2012 nov.
Is this because we have to wait for an update of the stdlib ?
I think the code is correct as I copied it from a slide from Scott meyers: Move Semantics, Rvalue References, Perfect Forwarding.
Thank you for your help.
For your information, the spurious copies occured because i did not add "const" in my CTOR by copy.
Best
Braces with auto will always end up in std::initializer_list<T> type. So basically what happens here you try to create a std::initializer_list<A> here for x instead of A what probably was your intent here. However your code should compile fine and that may be a bug in the VS latest CTP compiler.
To make x be of A you have 2 options:
Do not use auto here:
A x{ create() };
Do not use Uniform initialization here:
auto x(create());
Beside that I see 2 other issues in you code. Firstly signature of proper copy-constructor should look like that:
A(const A &) {printf("A CTOR by copy\n");}
Moreover it is not encouraged to return RValue references from functions. Instead you should return by value like that:
A create(){
A v;
return v; // std::move not needed here because C++11 compiler will put it for you here
}
or just:
A create(){
return A();
}
EDIT:
Oh and just to be politically correct A(A&&) is not "A CTOR by universal reference". It is "A CTOR by move" or "A CTOR by rvalue reference" depending on which level of abstraction you want to operate. Universal reference is always about templates and that specific type type deduction.
Please also read an article of Scott Meyers I attached in a different comment http://scottmeyers.blogspot.com/2012/10/copying-constructors-in-c11.html where the same C++ guru that you refer in your notes exactly explains the problems you face. Hope that helps.
The line
auto x{ create() };//this does not compile
creates an initializer_list and not an instance of A, see for example std::initializer_list.
If you write
A x{ create() };
the code compiles and the type of x will be A.
I wouldn't return A&& from create(), see Move Semantics and Compiler Optimizations. I haven't seen this in Scott's slides either.
The following code prints only A CTOR, see here. That is the best you can get: The object has to be created. Apart from that there are no unnecessary copy constructor or assignment operator calls.
#include <cstdio>
#include <utility>
using namespace std;
class A{
public:
A(){ printf("A CTOR\n");}
A(const A&) {printf("A CTOR by copy\n");}
A(A&&){ printf("A CTOR by universal reverence\n");}
A& operator=(const A& ) = delete;
};
A create(){
return A();
}
int main() {
A x{ create() };
return 0;
}
std::move is not the only way to get rid of the unnecessary copies. The compiler can do it for you, see for example What are copy elision and return value optimization? or Copy elision.