【问题标题】:Idiomatic way to prevent slicing?防止切片的惯用方法?
【发布时间】:2019-04-09 19:32:23
【问题描述】:

有时,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 的惯用方法是什么,这样就不能对任何派生类的实例进行切片?

【问题讨论】:

标签: c++ inheritance object-slicing


【解决方案1】:

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

【讨论】:

  • 请注意,这不会阻止像这样的显式切片:foo y = static_cast&lt;foo&amp;&gt;(x);。也就是说,也许这对 OP 来说不是问题。
  • 如果我理解正确,这是防止函数参数隐式转换的好方法
  • @user463035818 Yep。自从我问了那个问题后,我就一直在使用它。
  • 我将其视为反向 SFINAE。您制作要编译的重载,然后添加一个已删除的模板来阻止其他所有内容。
  • 其实我有点犹豫接受这个答案。该技术很棒,但实际上它为专门处理各种不需要的任务打开了大门,尽管如果我必须在 Java 语言“不惜一切代价防止一切可能的愚蠢行为”与 Python 语言“我们都是成年人”之间做出选择,那么我知道该选什么;)
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 但是我不能再复制foos :( 如果可能的话,我想防止只将 bar 复制到 foo
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-28
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    相关资源
    最近更新 更多