Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way to prevent slicing?

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?

like image 966
463035818_is_not_a_number Avatar asked Apr 09 '19 19:04

463035818_is_not_a_number


People also ask

How do you prevent object slices?

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.

What is object slicing and how can we prevent it?

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.


2 Answers

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.

like image 159
NathanOliver Avatar answered Sep 27 '22 20:09

NathanOliver


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
}
like image 41
Richard Hodges Avatar answered Sep 27 '22 21:09

Richard Hodges