Sometimes it can be an annoyance that c++ defaults to allow slicing. For example
struct foo { int a; };
struct bar : foo { int b; };
int main() {
bar x{1,2};
foo y = x; // <- I dont want this to compile!
}
This compiles and runs as expected! Though, what if I dont want to enable slicing?
What is the idiomatic way to write foo
such that one cannot slice instances of any derived class?
Object slicing can be prevented by making the base class function pure virtual thereby disallowing object creation. It is not possible to create the object of a class that contains a pure virtual method.
Object slicing happens when a derived class object is assigned to a base class object, additional attributes of a derived class object are sliced off to form the base class object. We can avoid above unexpected behavior with the use of pointers or references.
I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo
to
struct foo
{
int a;
foo() = default; // you have to add this because of the template constructor
template<typename T>
foo(const T&) = delete; // error trying to copy anything but a foo
template<typename T>
foo& operator=(const T&) = delete; // error assigning anything else but a foo
};
then you can only ever copy construct or copy assign a foo
to foo
. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.
Since 2011, the idiomatic way has been to use auto
:
#include <iostream>
struct foo { int a; };
struct bar : foo { int b; };
int main() {
bar x{1,2};
auto y = x; // <- y is a bar
}
If you wish to actively prevent slicing, there are a number of ways:
Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:
#include <iostream>
struct foo { int a; };
struct bar
{
bar(int a, int b)
: foo_(a)
, b(b)
{}
int b;
int get_a() const { return foo_.a; }
private:
foo foo_;
};
int main() {
bar x{1,2};
// foo y = x; // <- does not compile
}
Another more specialised way might be to alter the permissions around copy operators:
#include <iostream>
struct foo {
int a;
protected:
foo(foo const&) = default;
foo(foo&&) = default;
foo& operator=(foo const&) = default;
foo& operator=(foo&&) = default;
};
struct bar : foo
{
bar(int a, int b)
: foo{a}, b{b}
{}
int b;
};
int main() {
auto x = bar (1,2);
// foo y = x; // <- does not compile
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With