对象切片会破坏封装吗?
Does object slicing break encapsulation?
我的看法是,在 OOP 中,public 接口的一个作用是确保对象始终处于有效状态(这是您不能 memcpy
的一个重要原因到非 pod 类型)。例如。一个非常基本、迂腐的例子:(请忽略 java-like set/get):
class Restricted {
protected:
int a;
public:
Restricted() : a{0} {}
// always returns a non-negative number
auto get() const -> int {
return a;
}
auto set(int pa) -> void {
if (pa < 0)
a = 0;
else
a = pa;
}
auto do_something() {
// here I code with the assumption that
// a is not negative
}
};
在此示例中,class Restricted
的建模方式使得 Restricted
对象始终包含一个非负数。这就是我为 Restricted
定义有效状态的方式。通过查看界面,我可以说 Restricted ::get
总是 return 一个非负数。用户无法让 Restricted
持有负数。
a
受到保护,让选项轻松扩展 class。所以让我们用一个允许所有数字的类型扩展 Restricted
:
class Extended : public Restricted {
public:
Extended() { a = -1; }
auto set(int pa) -> void {
a = pa;
}
auto do_something() {
// now a can be negative, so I take that into account
}
};
乍一看一切正常。 Extended
不会修改 Restricted
或其行为。 Restricted
仍然是一样的,我们只是添加了另一种也允许负数的类型。
除了我们最初对 Restricted
的假设不再成立。用户可以轻松获得一个 Restricted
包含负数的对象(处于无效状态的对象),因为:
C++
允许在没有任何警告的情况下进行对象切片:
Restricted r = Extended{};
// or
Extended e;
e.set(-24);
Restricted r = e;
// and then:
r.do_something(); // oups
有些东西不对:
- 将
Extended
创建为 Restricted
的子 class 是错误的吗?如果是,为什么?
- 如果我希望
a
始终为非负数,那么将 a
设置为 protected
是否错误?为什么? protected
不应允许更改我的 class. 中的行为
C++
允许对象切片是不是错了?
- 以上所有
- 其他
C++ 给了你一把可以同时射击你双脚的枪。一直都是这样。
如果你想防止上述情况发生,删除基础class的复制构造函数,并让派生class的复制构造函数负责复制基础class.
你也可以使用私有继承。
您可以选择满足您的 class 设计要求的最佳解决方案。
was it wrong to set a as protected if I expect a to be always non-negative? Why?
是的,这是错误的。 Restricted
的接口 要求 a
是非负数。那就是类型设置的不变量。而且它没有 virtual
函数来允许派生的 class 覆盖该不变量。
通过违反该不变性(并且由于您奇怪地缺少 virtual
函数),Extended
正在打破 OOP 的基本规则:派生的 class 实例应该能够被视为 (public) 基础 class 的实例。那并不意味着切片;我的意思是,您应该能够将 Extended
传递给一个函数,该函数将 pointer/reference 传递给 Restricted
,并且一切都应该像在与 Extended
对话一样工作.
was it wrong to create Extended as a subclass of Restricted? If so, why?
错误的是:
将 a
设为 protected
而不是 private
.
使Restricted
的界面非virtual
.
is it wrong for C++ to allow object slicing?
没有
我的看法是,在 OOP 中,public 接口的一个作用是确保对象始终处于有效状态(这是您不能 memcpy
的一个重要原因到非 pod 类型)。例如。一个非常基本、迂腐的例子:(请忽略 java-like set/get):
class Restricted {
protected:
int a;
public:
Restricted() : a{0} {}
// always returns a non-negative number
auto get() const -> int {
return a;
}
auto set(int pa) -> void {
if (pa < 0)
a = 0;
else
a = pa;
}
auto do_something() {
// here I code with the assumption that
// a is not negative
}
};
在此示例中,class Restricted
的建模方式使得 Restricted
对象始终包含一个非负数。这就是我为 Restricted
定义有效状态的方式。通过查看界面,我可以说 Restricted ::get
总是 return 一个非负数。用户无法让 Restricted
持有负数。
a
受到保护,让选项轻松扩展 class。所以让我们用一个允许所有数字的类型扩展 Restricted
:
class Extended : public Restricted {
public:
Extended() { a = -1; }
auto set(int pa) -> void {
a = pa;
}
auto do_something() {
// now a can be negative, so I take that into account
}
};
乍一看一切正常。 Extended
不会修改 Restricted
或其行为。 Restricted
仍然是一样的,我们只是添加了另一种也允许负数的类型。
除了我们最初对 Restricted
的假设不再成立。用户可以轻松获得一个 Restricted
包含负数的对象(处于无效状态的对象),因为:
C++
允许在没有任何警告的情况下进行对象切片:
Restricted r = Extended{};
// or
Extended e;
e.set(-24);
Restricted r = e;
// and then:
r.do_something(); // oups
有些东西不对:
- 将
Extended
创建为Restricted
的子 class 是错误的吗?如果是,为什么? - 如果我希望
a
始终为非负数,那么将a
设置为protected
是否错误?为什么?protected
不应允许更改我的 class. 中的行为
C++
允许对象切片是不是错了?- 以上所有
- 其他
C++ 给了你一把可以同时射击你双脚的枪。一直都是这样。
如果你想防止上述情况发生,删除基础class的复制构造函数,并让派生class的复制构造函数负责复制基础class.
你也可以使用私有继承。
您可以选择满足您的 class 设计要求的最佳解决方案。
was it wrong to set a as protected if I expect a to be always non-negative? Why?
是的,这是错误的。 Restricted
的接口 要求 a
是非负数。那就是类型设置的不变量。而且它没有 virtual
函数来允许派生的 class 覆盖该不变量。
通过违反该不变性(并且由于您奇怪地缺少 virtual
函数),Extended
正在打破 OOP 的基本规则:派生的 class 实例应该能够被视为 (public) 基础 class 的实例。那并不意味着切片;我的意思是,您应该能够将 Extended
传递给一个函数,该函数将 pointer/reference 传递给 Restricted
,并且一切都应该像在与 Extended
对话一样工作.
was it wrong to create Extended as a subclass of Restricted? If so, why?
错误的是:
将
a
设为protected
而不是private
.使
Restricted
的界面非virtual
.
is it wrong for C++ to allow object slicing?
没有