防止切片的惯用方式?

时间:2019-04-09 19:32:24

标签: c++ inheritance object-slicing

有时,C ++默认允许切片可能会带来麻烦。例如

struct foo { int a; };
struct bar : foo { int b; };

int main() {
    bar x{1,2};
    foo y = x; // <- I dont want this to compile!
}

这个compiles and runs as expected!但是,如果我不想启用切片怎么办?

foo使得不能对任何派生类的实例进行切片的惯用方式是什么?

3 个答案:

答案 0 :(得分:20)

我不确定是否有命名的习惯用法,但是您可以向重载集中添加一个删除的函数,该函数比基类切片操作更匹配。如果您将foo更改为

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
};

然后,您只能复制构造或将foo分配给foo。任何其他类型都会选择功能模板,并且会出现有关使用已删除功能的错误信息。这确实意味着您的类以及使用它的类不再可以聚合。由于添加的成员是模板,因此不将它们视为复制构造函数或复制赋值运算符,因此您将获得默认的复制并移动构造函数和赋值运算符。

答案 1 :(得分:5)

自2011年以来,惯用的方法是使用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
}

如果您希望积极防止切片,可以采用多种方法:

通常,最可取的方法是使用封装,除非您特别需要继承(您通常不需要)()

#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

}

另一种更专业的方法可能是更改复制操作员周围的权限:

#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
}

答案 2 :(得分:3)

通过声明复制构造函数受保护,可以防止将基类复制到派生类的成员函数和基类本身之外:

struct foo {
    // ...
protected:
    foo(foo&) = default;
};