防止切片的惯用方法?

Idiomatic way to prevent 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 的惯用方式是什么,这样就不能对任何派生 class 的实例进行切片?

您可以通过声明复制构造函数 protected 来防止在派生 类 的成员函数和基本身之外复制基:

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

我不确定是否有命名的习语,但您可以将删除的函数添加到重载集,它比基础 classes 切片操作更匹配。如果将 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。任何其他类型都会选择函数模板,并且您会收到有关使用已删除函数的错误消息。这确实意味着您的 class 和使用它的 classes 不能再是一个集合。由于添加的成员是模板,因此它们不被视为复制构造函数或复制赋值运算符,因此您将获得默认的复制和移动构造函数和赋值运算符。

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