Idiomatic way to prevent slicing?

后端 未结 3 2056
执笔经年
执笔经年 2021-02-07 07:09

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         


        
相关标签:
3条回答
  • 2021-02-07 07:36

    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
    }
    
    0 讨论(0)
  • 2021-02-07 07:39

    You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:

    struct foo {
        // ...
    protected:
        foo(foo&) = default;
    };
    
    0 讨论(0)
  • 2021-02-07 07:50

    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.

    0 讨论(0)
提交回复
热议问题