...
Do not initialize an object of base class type with an object of derived class type, except through references, pointers, or pointer-like abstractions (such as std::unique_ptr, or std::shared_ptr
).
Noncompliant Code Example
In this noncompliant code example, an object of the derived Manager
type is passed by value to a function accepting a base Employee
type. This results in slicing the Manager
objects, resulting in information loss and unexpected behavior when the print()
function is called.
...
Code Block |
---|
Employee: Jane Doe |
Compliant Solution (Pointers)
Using the same class definitions as above, this compliant solution modifies the definition of f()
to require raw pointers to the object, removing the slicing problem:
...
Code Block |
---|
Employee: Joe Smith Employee: Bill Jones Manager: Jane Doe Assistant: Employee: Bill Jones |
Compliant Solution (References)
An improved compliant solution, which does not require guarding against null pointers within f()
, uses references instead of pointers:
Code Block | ||||
---|---|---|---|---|
| ||||
void f(const Employee &E) { std::cout << E; } int main() { Employee Coder("Joe Smith"); Employee Typist("Bill Jones"); Manager Designer("Jane Doe", Typist); f(Coder); f(Typist); f(Designer); } |
Compliant Solution (Noncopyable)
Both of the previous compliant solutions depend on consumers of the Employee
and Manager
types to be declared in a compliant manner with the expected usage of the class hierarchy. This compliant solution ensures that consumers are unable to accidentally slice objects by removing the ability to copy-initialize. If copy-initialization is attempted as in the original definition of f()
, the program is ill-formed and a diagnostic will be emitted. However, such a solution also requires the Manager
object to not attempt to copy-initialize its Employee
object, which subtly changes the semantics of the class hierarchy.
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream> #include <string> class Noncopyable { Noncopyable(const Noncopyable &) = delete; void operator=(const Noncopyable &) = delete; protected: Noncopyable() = default; }; class Employee : Noncopyable { std::string Name; protected: virtual void print(std::ostream &OS) const { OS << "Employee: " << getName() << std::endl; } public: Employee(const std::string &Name) : Name(Name) {} const std::string &getName() const { return Name; } friend std::ostream &operator<<(std::ostream &OS, const Employee &E) { E.print(OS); return OS; } }; class Manager : public Employee { const Employee &Assistant; // Note: this definition has been modified protected: void print(std::ostream &OS) const override { OS << "Manager: " << getName() << std::endl; OS << "Assistant: " << std::endl << "\t" << getAssistant() << std::endl; } public: Manager(const std::string &Name, const Employee &Assistant) : Employee(Name), Assistant(Assistant) {} const Employee &getAssistant() const { return Assistant; } }; // If f() were declared as accepting an Employee, the program would be // ill-formed because Employee cannot be copy-initialized. void f(const Employee &E) { std::cout << E; } int main() { Employee Coder("Joe Smith"); Employee Typist("Bill Jones"); Manager Designer("Jane Doe", Typist); f(Coder); f(Typist); f(Designer); } |
Noncompliant Code Example
This noncompliant code example uses the same class definitions of Employee
and Manager
from above, and attempts to store Employee
objects in a std::vector
. However, because std::vector
requires a homogeneous list of elements, slicing occurs.
Code Block | ||||
---|---|---|---|---|
| ||||
// In addition to the #includes from the previous example. #include <vector> void f(const std::vector<Employee> &V) { for (const auto &E : V) { std::cout << E; } } int main() { Employee Typist("Joe Smith"); std::vector<Employee> V{Typist, Employee("Bill Jones"), Manager("Jane Doe", Typist)}; f(V); } |
Compliant Solution
This compliant solution stores std::unique_ptr
smart pointers in the std::vector
, which eliminates the slicing problem:
Code Block | ||||
---|---|---|---|---|
| ||||
// In addition to the #includes from the previous example. #include <memory> #include <vector> void f(const std::vector<std::unique_ptr<Employee>> &V) { for (const auto &E : V) { std::cout << *E; } } int main() { std::vector<std::unique_ptr<Employee>> V; V.emplace_back(new Employee("Joe Smith")); V.emplace_back(new Employee("Bill Jones")); V.emplace_back(new Manager("Jane Doe", *V.front())); f(V); } |
Risk Assessment
Slicing results in information being lost, which could lead to abnormal program execution or denial of service attacks.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OOP33-CPP | Low | Probable | Medium | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
| 3072,3073 |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
CERT C++ Coding Standard | CTR56-CPP. Do not use pointer arithmetic on polymorphic objects |
CERT C Coding Standard |
Bibliography
[ISO/IEC 14882-2014] | 12.8, "Copying and Moving Class Objects" |
[Dewhurst 02] | Gotcha #38, "Slicing" |
[Sutter 00] | GotW #22: "Object Lifetimes - Part I" |
...