Saving a movable 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 that a good C ++ 11 compiler should define all these functions for you ( Visual Studio does not ), but I define them here to output debugging.
Now, if you want to write a class to store one of these classes, I can use pass-by-value, as you suggest:
class DataStore { Data data_; public: void setData(Data data) { data_ = std::move(data); } };
I use C ++ 11 to move semantics to move the value to the right place. Then I can use this DataStore as follows:
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 conclusion:
DataStore test: copy constructor move assignment DataStore test with rvalue: move assignment DataStore test with move: move constructor move assignment
This is normal. I have two moves in the last test, which may not be optimal, but the moves are usually cheap, so I can live with it. To make it more optimal, we will need to overload the setData function, which we will do later, but perhaps premature optimization at this stage.
Saving an unreadable class
But now imagine that we have a copyable but immutable 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;} };
Prior to C ++ 11, all classes were immutable, so expect to find many of them in the wild today. If I need to write a class to store this, I cannot use the move semantics, so probably I would 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 one copy, as you would expect.
Keeping an Uncovered Class
You can also have a movable but non-copyable 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 not std::unique_ptr class. In this case, I will probably write a class to store it as follows:
class UncopyableDataStore { UncopyableData data_; public: void setData(UncopyableData&& data) { data_ = std::move(data); } };
where I pass the 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 note that we have only one move, which is good.
Common containers
However, STL containers must be shared, they must work with all types of classes and be as optimal as possible. And if you really need the overall data warehousing implementation 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); } };
Thus, we get the best performance, whether we use uncoated or incompatible classes, but we must have at least two setData overloads that can lead to code duplication. Using:
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 this helps.