Storing a moveable and copyable class
Imagine you have this class:
class Data {
public:
Data() { }
Data(const Data& data) { std::cout << " copy constructor\n";}
Data(Data&& data) { std::cout << " move constructor\n";}
Data& operator=(const Data& data) { std::cout << " copy assignment\n"; return *this;}
Data& operator=(Data&& data) { std::cout << " move assignment\n"; return *this;}
};
Note, a good C++11 compiler should define all these functions for you (some old versions of Visual Studio don't) but I'm defining them here for debug output.
Now, if you wanted to write a class to store one of these classes I might use pass-by-value like you suggest:
class DataStore {
Data data_;
public:
void setData(Data data) { data_ = std::move(data); }
};
I am taking advantage of C++11 move semantics to move the value to the desired location. I can then use this DataStore
like this:
Data d;
DataStore ds;
std::cout << "DataStore test:\n";
ds.setData(d);
std::cout << "DataStore test with rvalue:\n";
ds.setData(Data{});
Data d2;
std::cout << "DataStore test with move:\n";
ds.setData(std::move(d2));
Which has the following output:
DataStore test:
copy constructor
move assignment
DataStore test with rvalue:
move assignment
DataStore test with move:
move constructor
move assignment
Which is fine. I have two moves in the last test which might not be optimum but moves are typically cheap so I can live with that. To make it more optimum we would need to overload the setData
function which we will do later but that's probably premature optimization at this point.
Storing an unnmovable class
But now imagine we have a copyable but unmovable class:
class UnmovableData {
public:
UnmovableData() { }
UnmovableData(const UnmovableData& data) { std::cout << " copy constructor\n";}
UnmovableData& operator=(const UnmovableData& data) { std::cout << " copy assignment\n"; return *this;}
};
Before C++11, all classes were unmovable so expect to find lots of them in the wild today. If I needed to write a class to store this I can't take advantage of move semantics so I would probably write something like this:
class UnmovableDataStore {
UnmovableData data_;
public:
void setData(const UnmovableData& data) { data_ = data; }
};
and pass by reference-to-const. When I use it:
std::cout << "UnmovableDataStore test:\n";
UnmovableData umd;
UnmovableDataStore umds;
umds.setData(umd);
I get the output:
UnmovableDataStore test:
copy assignment
with only one copy as you would expect.
Storing an uncopyable class
You could also have a movable but noncopyable class:
class UncopyableData {
public:
UncopyableData() { }
UncopyableData(UncopyableData&& data) { std::cout << " move constructor\n";}
UncopyableData& operator=(UncopyableData&& data) { std::cout << " move assignment\n"; return *this;}
};
std::unique_ptr
is an example of a movable but noncopyable class. In this case I would probably write a class to store it like this:
class UncopyableDataStore {
UncopyableData data_;
public:
void setData(UncopyableData&& data) { data_ = std::move(data); }
};
where I pass by rvalue reference and use it like this:
std::cout << "UncopyableDataStore test:\n";
UncopyableData ucd;
UncopyableDataStore ucds;
ucds.setData(std::move(ucd));
with the following output:
UncopyableDataStore test:
move assignment
and notice we now only have one move which is good.
Generic containers
The STL containers however need to be generic, they need to work with all types of classes and be as optimal as possible. And if you really needed a generic implementation of the data stores above it might look like this:
template<class D>
class GenericDataStore {
D data_;
public:
void setData(const D& data) { data_ = data; }
void setData(D&& data) { data_ = std::move(data); }
};
In this way we get the best possible performance whether we are using uncopyable or unmovable classes but we have to have at least two overloads of the setData
method which might introduce duplicate code. Usage:
std::cout << "GenericDataStore<Data> test:\n";
Data d3;
GenericDataStore<Data> gds;
gds.setData(d3);
std::cout << "GenericDataStore<UnmovableData> test:\n";
UnmovableData umd2;
GenericDataStore<UnmovableData> gds3;
gds3.setData(umd2);
std::cout << "GenericDataStore<UncopyableData> test:\n";
UncopyableData ucd2;
GenericDataStore<UncopyableData> gds2;
gds2.setData(std::move(ucd2));
Output:
GenericDataStore<Data> test:
copy assignment
GenericDataStore<UnmovableData> test:
copy assignment
GenericDataStore<UncopyableData> test:
move assignment
Live demo. Hope that helps.
std::vector
there isn't much choice, as it already had a pass by const reference signature. That can't be removed. For your own new classes, you have more options. – Gilolostd::vector
also? – CockayneA
above. Like two copies instead of one. – Giloloiterator
to be convertible to aconst_iterator
, so it will select the same overload. – Gilolo