...
When f()
is called with the designer
argument, the formal parameter in f()
is sliced and information is lost. When the Manager
object is object e
is printed, Employee::print()
is called instead of Manager::print()
, resulting in the following output:
...
Using the same class definitions as the noncompliant code example, this compliant solution modifies the definition of f()
to require raw pointers to the object, removing the slicing problem:.
Code Block | ||||
---|---|---|---|---|
| ||||
// ... restRemainder of code unchanged ... void f(const Employee *e) { if (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); } |
This compliant solution also complies with EXP34-C. Do not dereference null pointers in the implementation of f()
. With this definition, the the program correctly outputs :the following.
Code Block |
---|
Employee: Joe Smith Employee: Bill Jones Manager: Jane Doe Assistant: Employee: Bill Jones |
...
An improved compliant solution, which does not require guarding against null pointers within f()
, uses references instead of pointers:.
Code Block | ||||
---|---|---|---|---|
| ||||
// ... restRemainder of code unchanged ... 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); } |
...
Both 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 objects' ability to copy-initialize an object that derives from Noncopyable
. 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 restricts the Manager
object from attempting 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 { // Remainder of the definition is unchanged. std::string name; protected: virtual void print(std::ostream &os) const { os << "Employee: " << get_name() << std::endl; } public: Employee(const std::string &name) : name(name) {} const std::string &get_name() 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: ThisThe definition of Employee has been modified. // remainingRemainder of the definition is unchanged. protected: void print(std::ostream &os) const override { os << "Manager: " << get_name() << std::endl; os << "Assistant: " << std::endl << "\t" << get_assistant() << std::endl; } public: Manager(const std::string &name, const Employee &assistant) : Employee(name), assistant(assistant) {} const Employee &get_assistant() 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); } |
...
This noncompliant code example uses the same class definitions of Employee
and Manager
as in the previous examples noncompliant code example 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 <iostream> #include <string> #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 uses a vector of std::unique_ptr
smart pointers in the std::vector
, which objects, which eliminates the slicing problem:.
Code Block | ||||
---|---|---|---|---|
| ||||
// In addition to the #includes from the previous example#include <iostream> #include <memory> #include <memory><string> #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); } |
...
Slicing results in information loss, which could lead to abnormal program execution or denial-of-service attacks.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OOP51-CPP | Low | Probable | Medium | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| LANG.CAST.OBJSLICE | Object Slicing | ||||||
Helix QAC |
| C++3072 | |||||||
Parasoft C/C++test |
3072, 3073
| CERT_CPP-OOP51-a | Avoid slicing function arguments / return value | |||||||
Polyspace Bug Finder |
| CERT C++: OOP51-CPP | Checks for object slicing (rule partially covered) | ||||||
PVS-Studio |
| V1054 |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
SEI CERT C++ Coding Standard | ERR61-CPP. Catch exceptions by lvalue reference |
SEI CERT C Coding Standard |
Bibliography
[Dewhurst |
2002] | Gotcha #38, "Slicing" |
[ISO/IEC 14882-2014] | Subclause 12.8, "Copying and Moving Class Objects" |
[Sutter |
2000] | Item 40, "Object Lifetimes—Part I" |
...
...