A fundamental guideline when writing classes, is to relieve the user of the class from dealing with the internal state by exposing a strict interface. In C++, the copy-semantics of a class is part of the interface, and shall therefore also be as strict as necessary.
Classes should either behave as deep-copied or should fail to compile when copied. Copying a class should not have side effects where the resulting copied class can modify the original class. This may sound obvious, but there are many circumstances when, for example, a class requires a heap-allocated object accessed by a pointer member variable of some sort, for example std::shared_ptr, as follows:
class Engine {
public:
auto set_oil_amount(float v) { oil_ = v; }
auto get_oil_amount() const { return oil_; }
private:
float oil_{};
};
class YamahaEngine : public Engine {
//...
};
The programmer of the Boat class has left a rather loose interface without any precautions regarding copy semantics:
class Boat {
public:
Boat(std::shared_ptr<Engine> e, float l)
: engine_{e}
, length_{l}
{}
auto set_length(float l) { length_ = l; }
auto& get_engine() { return engine_; }
private:
// Being a derivable class, engine_ has to be heap allocated
std::shared_ptr<Engine> engine_;
float length_{};
};
Later, another programmer uses the Boat class and expects correct copy behavior:
auto boat0 = Boat{std::make_shared<YamahaEngine>(), 6.7f};
auto boat1 = boat0;
// ... and does not realize that the oil amount applies to both boats
boat1.set_length(8.56f);
boat1.get_engine()->set_oil_amount(3.4f);
This could have been prevented if the Boat class interface were made stricter by preventing copying. Now, the second programmer will have to rethink the design of the algorithm handling boats, but she won't accidentally introduce any subtle bugs:
class Boat {
private:
Boat(const Boat& b) = delete; // Noncopyable
auto operator=(const Boat& b) -> Boat& = delete; // Noncopyable
public:
Boat(std::shared_ptr<Engine> e, float l) : engine_{e}, length_{l} {}
auto set_length(float l) { length_ = l; }
auto& get_engine() { return engine_; }
private:
float length_{};
std::shared_ptr<Engine> engine_;
};
// When the other programmer tries to copy a Boat object...
auto boat0 = Boat{std::make_shared<YamahaEngine>(), 6.7f};
// ...won't compile, the second programmer will have to find
// another solution compliant with the limitations of the Boat
auto boat1 = boat0;