Synopsis:
It does that with copy constructors and copy assignment operators that allow the designer to implement deep copies. That makes the state of the destination object have the same value as the state of the source, but those states are independent. If the source changes its state later, that does not affect the state of the destination.
Code in Header File X.h
class X { public: X(); // default constructor x(const std::string& msg); // another constructor X(const X& x); // copy constructor X& operator=(const X& x); // copy assignment operator ~X(); // destructor std::string getMessage(); // method private: std::string* pMsg; // private data };
Code in Implementation File X.cpp
X::X() : pMsg(new std::string()) {} X::X(const std::string& s) : pMsg(new std::string(s)) {} X::X(const X& x) : pMsg(new std::string(*x.pMsg)) {} X& X::operator=(const X& x) { if(this != &x) { delete pMsg; pMsg = new std::string(*x.pMsg); } return *this; } X::~X() { delete pMsg; } std::string getMessage() { return *pMsg; }
C++ provides destructors that are called when the thread of execution leaves the scope where the instance was declared. This means that developers have tight control over how and when an instance's resources are returned to the process, for use by other instances. Users of C++ classes do not need to be aware of these resource allocations and deallocations. They simply use instances of the classes and the instances take care of all resource management.
The language also provides move construction and move assignment operations. Move construction builds an instance, taking ownership of another instance's state. This transfer is very efficient, usually effected with a pointer swap. Similarly, move assignment discards the destination's state and it takes ownership of the source's state.
Move operations usually occur only when the source is a temporary object, but the compiler can be coerced to execute a move on a non-temporary object by using the std::move(...) operation. Note that you need to be careful with this, as the source no longer owns it's former state and may be unable to continue. Essentially, the use of the std::move(...) is a promise not to use the source object any longer, or to reinitialize it in some effective way.
Construction-Destruction Guarantee:
Instances of C++ classes hold instances of their base classes and composed member data in their memory footprint. That means that a class's bases and composed members are an integral part of the class, and are always constructed when a class instance is constructed, and destroyed when it is destroyed. C++ Guarantees that! There is no way to prevent it.
For this reason, the language has to ensure that is always possible. It does so by providing compiler generated operations.Compiler Generated Operations:
C++ Compilers will generate construction and destruction operations if not provided by the class.
- If no constructors are declared for the class, the compiler will generate a default constructor that does default construction on each of its base classes1, if any, and member wise default construction on each member.
- The compiler will always generate a copy constructor if not declared by the class. The compiler generated copy constructor copies into the class instances of each base class of the source instance, and copies of each member of the source into its members.
- If not declared by the class, the compiler will always generate a copy assignment operator which does member-wise assignment from the source instance's base classes and member data.
- If the class does not declare a destructor, the compiler will always generate a destructor that does member-wise destruction of each of the class's base instances and members.
- For every class with bases and members all of which have correct copy, assignment, and destruction semantics, you don't need to, and should not, provide those operations. Primitive data and STL containers all have correct semantics for those operations, and classes that have only primitive and STL container data, have correct semantics for those operations. For almost every other class, the compiler generated operations will not be correct, and you must either provide them or disable them. Classes that contain pointers are examples of where you need to do this.
-
You can disable operations by declaring the operation followed by =delete. Here's an example:
Y(const Y& y) = delete;
The Rule of Threes:
To summarize, for the three operations of construction, assignment, and destruction, we have three choices:
- Allow the compiler to generate them - don't declare them - if all the classes bases and members have correct construction, assignment, and destruction semantics.
- Provide them by declaring and implementing each of the operations.
- Disable them by declaring each of the operations using = delete at the end of the declaration.