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.
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);
}
from the old C++98 i am aware, that return types a copied (by value) if not mentioned in the function declaration/definition otherwise with the address operator '&'.
Now i am playing around with the concepts of auto and decltype to let the compiler determin the return type. In an example i worte a class A where with exception of the default ctor any other ctors are deleted (class A is taken from a real project - and i investigate some issues). An object of the class A is contructed together with an etl::array (Embedded template library, Array created on the stack with fixed size), see example code below.
#include <etl/array.h>
#include <iostream>
class A {
public:
A(std::uint8_t val) : _val(val){}
A(A const&) = delete; // copy delete
A& operator=(A&) = delete; // copy assign delete
A(A&&) = default; // move default
A& operator=(A&&) = delete;// move assign delete
~A() = default;
void whoAmI(){std::cout << " I am A! " << std::endl;}
private:
std::uint8_t _val;
};
decltype(auto) X(std::uint8_t val) {
return etl::array<A,1>{A{val}};
}
int main()
{
decltype(auto) x = X(5U);
for (auto& it : x) {
it.whoAmI();
}
}
I would expect, that the etl::array will be copied in the main() routine and assigned to the local variable x. I would not expect to have a copy of A in the array, due to the deleted copy ctor. However, the code compiles and i am able to call the function on the element of the etl::array. I cannot understand why this is working and why it is compiling at all? And I wonder what type decltype(auto) finally is. I have chosen decltype(auto) because of Scott-Meyers Item 2 and Item 3. To item 3, i am not sure to have a complete understanding on the decltype topic..
When I step through the code it works fine, leaving me pussled behind..
Any help on this topic is highly appreciated!
Thank you very much for your help, it is enlightening to me.
Now i finally know why it's working :-D - you made my day!
decltype(auto) is used to determine the type and the value category of an expression. When used as a return type, it lets the function decide what the returned value type should be, based on the expression in the return statement. In this example, since you are using a temporary in the returned expression, it will deduce to an rvalue.
In this declaration:
decltype(auto) x = X(5U);
the syntax is copy-initialization, which has the effect of initializing the variable x from the expression X(5U). You have a defaulted move-constructor, and the compiler uses this constructor to initialize x from the rvalue returned from X.
Note that from C++17, due to mandatory copy-elision, you could even delete the move constructor, and the code is still valid, since there is no constructor needed to initialize x.
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).
I have been learning about (N)RVO for a last few days. As I read on cppreference in the copy elision article, for C++14 :
... the compilers are permitted, but not required to omit the copy- and move- (since C++11)construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
So, either copy or move constructor must be present and accessible. But in the code bellow:
#include <iostream>
class myClass
{
public:
myClass() { std::cout << "Constructor" << std::endl; }
~myClass() { std::cout << "Destructor" << std::endl; }
myClass(myClass const&) { std::cout << "COPY constructor" << std::endl;}
myClass(myClass &&) = delete;
};
myClass foo()
{
return myClass{};
}
int main()
{
myClass m = foo();
return 0;
}
I got the following error: test.cpp: In function 'myClass foo()':
test.cpp:15:17: error: use of deleted function 'myClass::myClass(myClass&&)'
return myClass{};. I get this error even if I don't call foo() from the main(). The same issue with NRVO.
Hence the move constructor is always required, isn't it? (while the copy is not, I checked it)
I do not understand where compiler needs a move constructor. My only guess is that it might be required for constructing a temporary variable, but it sounds doubtful. Do someone knows an answer?
About the compiler: I tried it on g++ and VS compilers, you can check it online: http://rextester.com/HFT30137.
P.S. I know that in C++17 standard the RVO is obligated. But the NRVO isn't, so I want to study out what is going on here to understand when I can use NRVO.
Cited from cppreference:
Deleted functions
If, instead of a function body, the special syntax = delete ; is used, the function is defined as deleted.
...
If the function is overloaded, overload resolution takes place first, and the program is only ill-formed if the deleted function was selected.
It's not the same thing if you explicitly define the move constructor to be deleted. Here because of the presence of this deleted move constructor, although the copy constructor can match, the move constructor is better, thus the move constructor is selected during overload resolution.
If you remove the explicit delete, then the copy constructor will be selected and your program will compile.
This question already has answers here:
Why copy constructor not invoked here
(2 answers)
Closed 8 years ago.
I have read some articles concerning this topic but still have problems to compile my own code.
I have class A:
class A
{
public:
List<int> data;
A(){}
A(A&){}
A& operator= (const A& a)
{
// copy the data from a to data
}
};
Class B will call class A:
class B
{
public:
A makeA()
{
A a;
return a;
}
A getA()
{
A a = makeA();
return a;
}
};
When I compile my code with g++ under Linux, I got:
no matching function for call to 'A::A(A)'.
It seems that the compiler has simply ignored the assignment operation. Can you help me out of this?
In order for this to compile, your copy constructor must take its parameter by const reference:
A(const A&){}
Adding const to your constructor signature fixes this problem (demo on ideone).
Since you are defining an assignment operator and a copy constructor, you should strongly consider adding a desctructor ~A() (see the Rule of Three).
The assignment operator is not used here.
A a = makeA();
This line is an initialization; it uses the copy constructor to copy the value returned by makeA into a. The compiler is complaining because A::A(A&) can't be used with a temporary; change it to the much more common form A(const A&) and things will be much better.
#Peter is right. The copy constructor A(A&){} wants to be A(const A&){}, instead. The reason is that A(A&){} tells the compiler to prepare to modify the A passed to the copy constructor, which does not really make sense, and certainly does not make sense in case the A you pass is a temporary.