C.80: Use =default if you have to be explicit about using the default semantics
The compiler is more likely to get the default semantics right and you cannot implement these functions better than the compiler.
classTracer{
string message;public:Tracer(const string& m): message{m}{
cerr <<"entering "<< message <<'\n';}~Tracer(){ cerr <<"exiting "<< message <<'\n';}// Because we defined the destructor, we must define the copy and move operations.Tracer(const Tracer&)=default;
Tracer&operator=(const Tracer&)=default;Tracer(Tracer&&)=default;
Tracer&operator=(Tracer&&)=default;};
C.81: Use =delete when you want to disable default behavior (without wanting an alternative)
In a few cases, a default operation is not desirable.
Note that deleted functions should be public.
classImmortal{public:~Immortal()=delete;// do not allow destruction// ...};voiduse(){
Immortal ugh;// error: ugh cannot be destroyed
Immortal* p =new Immortal{};delete p;// error: cannot destroy *p}
C.82: Don't call virtual functions in constructors and destructors
There is nothing inherently wrong with calling virtual functions from constructors and destructors. The semantics of such calls is type safe. However, experience shows that such calls are rarely needed, easily confuse maintainers, and become a source of errors when used by novices.
The function called will be that of the object constructed so far, rather than a possibly overriding function in a derived class. This can be most confusing.
Worse, .a direct or indirect call to an unimplemented pure virtual function from a constructor or destructor results in undefined behavior
classBase{public:virtualvoidf()=0;// not implementedvirtualvoidg();// implemented with Base versionvirtualvoidh();// implemented with Base versionvirtual~Base();// implemented with Base version};classDerived:publicBase{public:voidg()override;// provide Derived implementationvoidh()final;// provide Derived implementationDerived(){// BAD: attempt to call an unimplemented virtual functionf();// BAD: will call Derived::g, not dispatch further virtuallyg();// GOOD: explicitly state intent to call only the visible versionDerived::g();// ok, no qualification needed, h is finalh();}};
Note that calling a specific explicitly qualified function is not a virtual call even if the function is virtual.
C.83: For value-like types, consider providing a noexceptswap function
A swap can be handy for implementing a number of idioms, from smoothly moving objects around to implementing assignment easily to providing a guaranteed commit function that enables strongly error-safe calling code.
classFoo{public:voidswap(Foo& rhs)noexcept{
m1.swap(rhs.m1);
std::swap(m2, rhs.m2);}private:
Bar m1;int m2;};
Providing a non-member swap function in the same namespace as your type for callers' convenience.
swap is widely used in ways that are assumed never to fail and programs cannot easily be written to work correctly in the presence of a failing swap.
The standard-library containers and algorithms will not work correctly if a swap of an element type fails.
voidswap(My_vector& x, My_vector& y){auto tmp = x;// copy elements
x = y;
y = tmp;}
Above is not just slow - if a memory allocation occurs for the elements in tmp, this swap could throw and would make STL algorithms fail if used with them.
C.85: Make swapnoexcept
If a swap tries to exit with an exception, it's a bad design error and the program had better terminate. See C.84
C.86: Make == symmetric with respect to operand types and noexcept
Asymmetric treatment of operands is surprising and a source of errors where conversions are possible.
== is a fundamental operation and programmers should be able to use it without fear of failure.
classB{
string name;int number;booloperator==(const B& a)const{return name == a.name && number == a.number;}// ...};
If a class has a failure state, like double's NaN, there is a temptation to make a comparison against the failure state throw.
The alternative is to make two failure states compare equal and any valid state compare false against the failure state.
This rule applies to all the usual comparison operators: !=, <, <=, >, and >=.
C.87: Beware of == on base classes
It is really hard to write a foolproof and useful == for a hierarchy.
classB{
string name;int number;virtualbooloperator==(const B& a)const{return name == a.name && number == a.number;}// ...};classD:B{char character;virtualbooloperator==(const D& a)const{return name == a.name && number == a.number && character == a.character;}// ...};
B b =...
D d =...
b == d;// compares name and number, ignores d's character
d == b;// error: no == defined
D d2;
d == d2;// compares name, number, and character
B& b2 = d2;
b2 == d;// compares name and number, ignores d2's and d's character
Of course there are ways of making == work in a hierarchy, but the naive approaches do not scale
This rule applies to all the usual comparison operators: !=, <, <=, >, >=, and <=>.
C.89: Make a hashnoexcept
Users of hashed containers use hash indirectly and don't expect simple access to throw. It's a standard-library requirement.
If you have to define a hash specialization, try simply to let it combine standard-library hash specializations with ^ (xor). That tends to work better than "cleverness" for non-specialists.
C.90: Rely on constructors and assignment operators, not memset and memcpy
The standard C++ mechanism to construct an instance of a type is to call its constructor.
As specified in guideline C.41: a constructor should create a fully initialized object. No additional initialization, such as by memcpy, should be required.
A type will provide a copy constructor and/or copy assignment operator to appropriately make a copy of the class, preserving the type's invariants.
Using memcpy to copy a non-trivially copyable type has undefined behavior. Frequently this results in slicing, or data corruption.