This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 15.0.1 - Special member functions shall be provided appropriately
Category: Required
Analysis: Decidable, Single Translation Unit
Amplification
For the purposes of this rule, a special member function is said to be customized if it is user-provided and not
defaulted.
All out-of-class definitions of the destructor, copy operations, and move operations for a class shall be placed in a single file.
In addition, a class shall satisfy all of the requirements defined within this rule that apply to it. For instance, a class that is used as a base
class and that has a customized destructor shall comply with the requirements within all of the following requirement sections.
Heading: Requirements for all classes on copyability and movability
A class T is copy-constructible if the expression std::is_copy_constructible_v< T > is true,
and similarly for move-constructible, move-assignable, and copy-assignable.
A class shall belong to exactly one of the following categories (other combinations are not permitted):
- Unmovable — it is not copy-constructible, not move-constructible, not copy-assignable and not
move-assignable; or
- Move-only — it is move-constructible (and optionally move-assignable) but neither copy-constructible nor
copy-assignable; or
- Copy-enabled — it is copy-constructible and move-constructible and can optionally also be both
copy-assignable and move-assignable.
Category |
Move constructible |
Move assignable |
Copy constructible |
Copy assignable |
Unmovable |
No |
No |
No |
No |
Move-only |
Yes |
No |
No |
No |
::: |
Yes |
Yes |
No |
No |
Copy-enabled |
Yes |
No |
Yes |
No |
::: |
Yes |
Yes |
Yes |
Yes |
Heading: Requirements in the presence of customized copy or move operations
If a class has customized copy or move operations, it shall have a customized destructor.
Heading: Requirements in the presence of customized destructors
The definition of any customized destructor shall contain at least one statement that is neither a compound statement nor a
null statement. A class with such a destructor is regarded by this rule as managing a resource.
Additionally:
- If the class is unmovable, it is defined to be a scoped manager [1].
- If the class is move-only, it shall have a customized move constructor. If it is move-assignable, it shall also have
a customized move assignment operator. Such a class is defined to be a unique manager [2].
- If the class is copy-enabled, it shall have a customized copy constructor and its move constructor shall either be
customized or not declared. If it is copy-assignable, it shall also have a customized copy-assignment operator and the
move operations shall either both be customized or both not be declared. Such a class is defined to be a general manager [3].
Heading: Requirements in the presence of inheritance
A class that is used as a public base class shall either:
- Be an unmovable class that has a (possibly inherited)
public virtual destructor; or
- Have a
protected non-virtual destructor.
Note: these destructors shall either be defined as = default or customized (and non-empty).
Rationale
Language rules determining which user-declared special member functions suppress which of the compiler-provided functions are subtle and,
for legacy reasons, the combinations produced may be semantically unsound (see [depr.impldec] in the C++ Standard).
This rule takes advantage of reasonable language-provided defaults and, in particular, avoids the need to explicitly implement these defaults
within the code; code that is, or attempts to be, equivalent to the compiler-provided functions is superfluous and may have subtle behavioural
differences.
More specifically, application of the "Rule of Zero" in class definitions is encouraged — i.e. when a class does not declare any of the move
constructor, copy constructor, move-assignment operator, copy-assignment operator, or destructor special
member functions. A class following the "Rule of Zero" can be unmovable, move-only or copy-enabled, depending on the
properties of the class’s members and base classes.
The requirements on copyability and movability enforce the use of semantically sound combinations of the special member functions. In
particular, they ensure that copy-constructible types are always move-constructible.
The requirements on classes with customized destructors cover the cases where resource management is involved and ensure that, when a
class directly handles a resource, customized code will be called when an instance of the class is copied, moved or destroyed.
The requirements on base classes reduce the risk of slicing and deleting a derived class instance through a base class pointer, when the base class
does not have a virtual destructor:
- Compliance with requirement 1 ensures that these risks are completely prevented.
- Compliance with requirement 2 ensures that these risks are prevented for code that does not have privileged access to the base class.
Compliance with this rule addresses the vulnerabilities covered by the "Rule of Zero", the "Rule of Five" and similar rules in other C++
guidelines. It also covers the vulnerabilities identified within the pre-C++11 "Rule of Three" — see the requirements for general manager [3]
(above).
Exception
An aggregate, which cannot have a user-declared destructor, may be used as a public base class — this allows empty base class
optimization for mix-in and tag types.
Example
struct MyTagClass {}; // Compliant - Rule of Zero (empty class)
struct MyValue // Compliant - Rule of Zero
{
int32_t val { 42 };
};
struct PolyBaseWrong // Non-compliant - base class that is not an aggregate
{ // and has no virtual destructor.
virtual void doIt(); // Additionally, slicing may occur.
};
struct DerivedWrong : PolyBaseWrong {};
struct PolyBase // Compliant - unmovable base class with virtual public
{ // destructor
virtual void doIt() = 0;
virtual ~PolyBase() = default;
PolyBase & operator=( PolyBase && ) = delete; // This makes the class unmovable
};
struct Derived : PolyBase
{
// Class definition
};
struct NonEmptyDestructor // Non-compliant - copy-enabled class with a customized
// destructor but non-customized
{ // copy-operations
~NonEmptyDestructor() // Non-compliant - customized destructor has empty body
{
{
// Still empty
;
}
}
};
struct Locker // Compliant - scoped manager
{
explicit Locker( std::mutex & m ) :
m { m }
{
m.lock();
}
~Locker()
{
m.unlock();
}
Locker & operator=( Locker && ) = delete; // This makes the class unmovable
private:
std::mutex & m;
};
struct NonMovable // Non-compliant - copy-constructible, but not
{ // move-constructible
NonMovable( NonMovable const & );
NonMovable( NonMovable && ) = delete;
};
struct Aggregate { };
struct Child : Aggregate // Compliant by exception - base class without
{ // destructor is an aggregate
};
Glossary
[1] Scoped manager
A manager class [4], as defined in M23_372: MISRA C++ 2023 Rule 15.0.1.
[2] Unique manager
A manager class [4], as defined in M23_372: MISRA C++ 2023 Rule 15.0.1.
[3] General manager
A manager class [4], as defined in M23_372: MISRA C++ 2023 Rule 15.0.1.
[4] Manager class
A class that is either a scoped manager [1], a unique manager [2], or a general manager [3] as defined in
M23_372: MISRA C++ 2023 Rule 15.0.1.
Copyright The MISRA Consortium Limited © 2023