x of type std::vector<std::string> is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called only if x.empty() returns false.x.push_back(std::move(s));
std::cout << s << '\n'; // OK
std::cout << s.size() << '\n'; // OK
std::cout << s[0] << '\n'; // ERROR
std::cout << s.front() << '\n'; // ERROR
s = "new value"; // OK
std::vector as an example:
| Invariant | Meaning |
| ———————————————————- | ————————————————– |
| 0 <= size() <= capacity() | Logical and memory consistency. |
| data() != nullptr or size() == 0 | If non-empty, data() must point to valid memory. |
| elements are contiguous | Equivalent to an array of T. |
| begin() + size() == end() | Iterator arithmetic consistency. |
| capacity() never decreases unless you explicitly shrink. | capacity() only grows or resets. |
| data() points to same block until reallocation. | Stable storage invariant. |In most of the classes, the generated special move member functions bring moved-from objects into a state where the assignment operator and destructor work just fine. However, provided each moved-from member is assignable and destructible, both an assignment and the destruction of the moved-from object as a whole should work well:
class Customer {
void foo() {
std::string customer = "Rocky";
name = std::move(customer);
// both _name and _value are valid but unspecified
} // destructor of Customer is expected to destory _name and _value too
std::string name;
std::vector<int> value;
};
Example:
#include <array>
#include <thread>
class Tasks {
private:
std::array<std::thread,10> threads; // array of threads for up to 10 tasks
int numThreads{0}; // current number of threads/tasks
public:
Tasks() = default;
// pass a new thread:
template <typename T>
void start(T op) {
threads[numThreads] = std::thread{std::move(op)};
++numThreads;
}
//...
// OOPS: enable default move semantics:
Tasks(Tasks&&) = default;
Tasks& operator=(Tasks&&) = default;
// at the end wait for all started threads:
~Tasks() {
// numThreads and the number of active threads
// in threads[] is inconsistent
for (int i = 0; i < numThreads; ++i) {
threads[i].join();
}
}
};
int main()
{
try {
Tasks ts;
ts.start([]{
std::this_thread::sleep_for(std::chrono::seconds{2});
std::cout << "\nt1 done" << std::endl;
});
ts.start([]{
std::cout << "\nt2 done" << std::endl;
});
// OOPS: move tasks:
Tasks other{std::move(ts)};
}
catch (const std::exception& e) {
std::cerr << "EXCEPTION: " << e.what() << std::endl;
}
}
The move operations of containers move the elements, so that after the std::move(), ts no longer contains any thread objects that represent a running thread. Therefore, numThreads is just copied, which means that we create an inconsistent/invalid state. The destructor will finally loop over the first two elements calling join(), which throws an exception (which is a fatal error in a destructor).
std::move() (or only use a limited set of operations).class Card {
public:
Card(const std::string& v) : _value(v)
{
assertValidCard(_value);
}
std::string getValue() const {
return _value;
}
private:
std::string _value;
};
In this class, one of the expected invariat is that _value should always follow a specific pattern. For example, _value = "queen-of-hearts". However, the _value in the moved_from object does not adhere to this pattern.
The options for fixing this class are as follow:
getValue() or other member functions.Card might have no value.
void print(const Card& c) {
std::string val{ c.getValue() };
auto pos = val.find("-of-");
// find position of substring
If (pos != std::string::npos) {
// check whether it exists
std::cout << val.substr(0, pos) << ' ' << val.substr(pos+4) << '\n';
} else {
std::cout << "no value\n";
}
}
Two member values are expected to be consistent. For example:
class IntString {}
private:
int val;
std::string sval;
public:
IntString(int i=0) : val{i}, sval{std::to_string(i)}
{ }
oid dump() {
std::cout << val << "/" << sval << "\n";
}
;
class SharedInt {
private:
std::shared_ptr<int> sp;
public:
explicit SharedInt(int val)
: sp{std::make_shared<int>(val)} {}
// Error: assumes there is always an int value
std::string asString() const {
return std::to_string(*sp);
}
};
Resolutions:
class SharedInt {
private:
std::shared_ptr<int> sp;
// define moved-from value
inline static std::shared_ptr<int> movedFromValue{std::make_shared<int>(0)};
public:
explicit SharedInt(int val)
: sp{std::make_shared<int>(val)} {}
// Error: assumes there is always an int value
std::string asString() const {
return std::to_string(*sp);
}
// Move semantics
SharedInt(SharedInt&& rhs)
: sp{std::move(rhs.sp)}{
rhs.sp = movedFromValue;
}
SharedInt& operator=(SharedInt&& rhs) {
if (this != &rhs) {
sp = std::move(rhs.sp);
rhs.sp = moveFromValue;
}
return *this
}
};