防止切片的惯用方法?
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
}
有时,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
}