Classes and class hierarchies
C.44
Prefer default constructors to be simple and non-throwing
Reason
Being able to set a value to "the default" without operations that might fail simplifies error handling and reasoning about move operations.
Example, problematic
template<typename T>
// elem points to space-elem element allocated using new
class Vector0 {
public:
Vector0() :Vector0{0} {}
Vector0(int n) :elem{new T[n]}, space{elem + n}, last{elem} {}
// ...
private:
own<T*> elem;
T* space;
T* last;
};
This is nice and general, but setting a Vector0 to empty after an error involves an allocation, which might fail. Also, having a default Vector represented as {new T[0], 0, 0} seems wasteful. For example, Vector0<int> v[100] costs 100 allocations.
Example
template<typename T>
// elem is nullptr or elem points to space-elem element allocated using new
class Vector1 {
public:
// sets the representation to {nullptr, nullptr, nullptr}; doesn't throw
Vector1() noexcept {}
Vector1(int n) :elem{new T[n]}, space{elem + n}, last{elem} {}
// ...
private:
own<T*> elem {};
T* space {};
T* last {};
};
Using {nullptr, nullptr, nullptr} makes Vector1{} cheap, but a special case and implies run-time checks. Setting a Vector1 to empty after detecting an error is trivial.
Enforcement
- Flag throwing default constructors