I am trying to clarify-understand move semantics and, for that, I wrote the following code. I used a raw pointer as a data member only to practice in finding all the dangerous spots and also apply idioms like copy & swap.
#include <iostream>
#include <utility>
class Example {
protected:
int* intPtr;
public:
Example() : intPtr{nullptr} {
allocate();
std::cout << "Example() called" << "\n";
}
Example(int value) : intPtr{nullptr} {
allocate();
assign(value);
std::cout << "Example(int) called" << "\n";
}
~Example() {
deallocate();
std::cout << "~Example called" << "\n";
}
Example(const Example& anExample) :
intPtr{anExample.intPtr ? new int(*anExample.intPtr) : nullptr} {
std::cout << "Example(const Example&) called" << "\n";
}
Example(Example&& anExample) noexcept {
intPtr = anExample.intPtr;
anExample.intPtr = nullptr;
std::cout << "Example(Example&&) called" << "\n";
}
Example& operator=(Example&& anExample) noexcept {
if(this != &anExample){
intPtr = anExample.intPtr;
anExample.intPtr = nullptr;
std::cout << "Move assignment op called" << "\n";
}
return *this;
}
Example& operator=(Example anExample) {
std::swap(intPtr, anExample.intPtr);
std::cout << "Copy assignment op called \n";
return *this;
}
void assign(int value) {
if (intPtr != nullptr && *intPtr!=value)
*intPtr = value;
else {
std::cout << __FUNCTION__ << ": intPtr is either null, or its memory contains already the value you want.\n";
}
}
private:
void allocate() {intPtr = new int{};}
void deallocate() {delete intPtr;}
friend std::ostream& operator<<(std::ostream& strm, const Example& anExample) {
return strm << *anExample.intPtr;
}
};
int main() {
Example ex1;
std::cout << "----------------" <<std::endl;
Example ex2{ex1};
std::cout << "----------------" <<std::endl;
Example ex3 = ex2; // invokes copy constructor
std::cout << "----------------" <<std::endl;
Example ex4;
std::cout << "----------------" <<std::endl;
ex4 = ex3; // invokes copy assignement operator
std::cout << "----------------" <<std::endl;
Example ex5 = std::move(ex1); // invokes move constructor
std::cout << "----------------" <<std::endl;
ex4 = std::move(ex5); // compilation error!
return 0;
}
The last line gives the following error
main.cxx:116:22: error: ambiguous overload for ‘operator=’ (operand types are ‘Example’ and ‘std::remove_reference<Example&>::type’ {aka ‘Example’})
116 | ex4 = std::move(ex5);
| ^
main.cxx:36:12: note: candidate: ‘Example& Example::operator=(Example&&)’
36 | Example& operator=(Example&& anExample) noexcept {
| ^~~~~~~~
main.cxx:45:12: note: candidate: ‘Example& Example::operator=(Example)’
45 | Example& operator=(Example anExample) {
| ^~~~~~~~
Why is the right hand side's type std::remove_reference<Example&>::type
. What am I doing wrong here and how to invoke the move-assignement operator properly?
Every other comment is welcome.
Example& operator=(Example anExample)
is a general assignment operator. It copies lvalues and moves rvalues.
If you want to distinguish copy from move assignment you need Example& operator=(const Example & anExample)
.
Alternatively you could remove Example& operator=(Example&& anExample)
.