Why is this an issue?
Slicing happens when an object from a derived type is cast to an object of one of its base classes. When this happens, the new object will not have
the data member variables specific to the derived type.
The following example illustrates the unintended loss of information.
struct PartData {
int uuid;
std::string manufacturer;
};
// Use inheritance to share common data definitions.
struct TireData : PartData {
Color color;
TireType type;
};
void orderBike(TireData tire, ...) {
std::vector<PartData> parts;
// Noncompliant: the vector does not store the tire color and type.
parts.push_back(tire);
// ...
}
How to fix it
This code defect usually results from using values instead of references or pointers to pass polymorphic objects to functions.
It is usually a good idea to design a base class so that slicing cannot happen: it can be abstract or non-copiable. The standard library follows
this practice and prevents copying, for example, std::ostream
objects.
Code examples
Noncompliant code example
This example illustrates the problem with a FileStream
concrete class and a derived buffered implementation,
BufferedFileStream
.
class FileStream {
// ...
public:
FileStream(std::string_view file_path);
virtual ~Stream() = default;
virtual void write(int x);
};
class BufferedFileStream : public FileStream {
std::array<char, 1024> buffer;
// ...
public:
BufferedFileStream(std::string_view file_path);
~BufferedFileStream() { flushBuffer(); }
void write(int x) {
// Write to the buffer; flush if it is full.
// ...
}
};
void writeAll(FileStream stream, std::vector<int> const& ints);
void application(int userId) {
BufferedFileStream stream;
stream.write(userId);
std::vector<int> data = getData();
writeAll(stream, data); // Noncompliant: stream is sliced, and its buffer may be lost or written out-of-sequence
}
Compliant solution
To prevent slicing from happening, the base class can be made non-copyable. This implies passing a reference instead of a copy to
writeAll
.
class FileStream {
// ...
public:
FileStream(std::string_view file_path);
FileStream(FileStream const&) = delete;
virtual ~Stream() = default;
virtual void write(int x);
};
class BufferedFileStream : public FileStream {
std::array<char, 1024> buffer;
// ...
public:
BufferedFileStream(std::string_view file_path);
~BufferedFileStream() { flushBuffer(); }
void write(int x) {
// Write to the buffer; flush if it is full.
// ...
}
};
void writeAll(FileStream& stream, std::vector<int> const& ints);
void application(int userId) {
BufferedFileStream stream;
stream.write(userId);
std::vector<int> data = getData();
writeAll(stream, data); // Compliant, no slicing, no dataloss.
}
Going the extra mile
When slicing is actually required, it is best to make it explicit to avoid the element of surprise. You can create a dedicated member function for
this purpose, with its own documentation. This goes well in hand with non-copyable classes.
Resources
External coding guidelines