防止切片的惯用方法?

Posted

技术标签:

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

有时,c++ 默认允许切片可能会令人烦恼。例如

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

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

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

foo 的惯用方法是什么,这样就不能对任何派生类的实例进行切片?

【问题讨论】:

最简单的方法是不使用继承。你有foobar 的成员变量。 @KevinZ ***.com/a/55600208/4117728 【参考方案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 x1,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 x1,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) 
    : fooa, bb
    

    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

以上是关于防止切片的惯用方法?的主要内容,如果未能解决你的问题,请参考以下文章

为啥切片 params 散列会对批量分配造成安全问题?

从 Golang 中的数组中选择元素的最惯用方法?

时间切片的实现和调度(原创2.6万字)

时间切片的实现和调度(原创2.6万字)

c_cpp C.67:基类应该禁止复制,如果需要“复制”,则提供虚拟克隆。原因:为了防止切片,因为

Python 切片操作方法,我知道 Python 切片,但是如何使用内置切片对象呢?