cpp核心指南中的列表C.67说:基类应该禁止复制,如果"复制"则提供虚拟克隆。是理想的。
如果在基础中将复制构造函数定义为已删除,则对基类和所有派生类也会禁止移动操作。
另一方面,移动操作可以提高性能。我的问题是,当我们设计一个类层次结构时,我们应该采用什么样的现实方法?
假设我们有以下类层次结构?我们应该如何设计A和B来正确支持复制和移动操作。
class A{
public:
A(const std::string& as) = deleted;
//should we define other copy/move operators?
virtual void foo();//
virtual ~A();//
private:
std::string s;
};
class B: public A{
public:
//how do we define copy/move operators?
void foo() override;
~B() override;
private:
std::vector<std::string> vs;
};
答案 0 :(得分:0)
首先,请注意clone
不是“多态类型对象的副本”:它们只是具有不同语义的不同操作。
virtual
,因为缺少当前对象,其类可以提供行为。)用户必须指定类型,并且应该期望复制的对象被“重新解释”(切片)为已知的,指定的类型,如果它实际上是派生类型。clone
将动态已知派生类的对象复制为 类的另一个对象。由于参数确定结果的类型,因此用户不能指定它,并且实际上不(静态地)知道所选择的内容。 (堆分配是必然结果。)您想要哪一个取决于您希望结果具有的生命周期和类型(包括“与该类型相同的类型”作为选择)。我发现有人会写一个副本(例如,一个指定的类型是具体基类的按值参数)并且对他们选择的内容感到惊讶。
接下来,请注意,除了赋值(必须通过引用)之外,抽象类不必担心切片。赋值可以是virtual
(因为对象已经存在),但它不能静态地避免切片,因为类型不需要匹配:
struct B {
virtual ~B()=default;
virtual B& operator=(const B&)=default;
// ...
};
struct D1 : B {
D& operator=(const B&) override;
// ...
};
struct D2 : B {/* ... */};
void f() {
B &&b=D1();
b=D2(); // ok
}
作业必须只使用共同的B
部分,或者......抛出?如果赋值失败,则将其作为函数提供将更加清晰:如果类型不同,可能bool assign_like(const B&) &;
返回false
。
因此,如果我们想避免切片的风险,我们确实必须至少做一些关于任务的事情。删除赋值运算符的核心指南思想是合理的,但我只想在每个抽象基类中使它protected
。
如果你从不从具体类继承,那就是你需要防止切片的所有内容:叶子类中的隐式(public
)特殊成员函数不切片,自动使用base的相应SMF,可以根据需要自动使用。 (例如,具体类可以按值传递。)
在具体的基类中,每个SMF有两个选择:
protected
,否认故意复制对象的可能性(即使源的完整对象类型是静态知道的)。clone
之间的区别(以及“完全virtual
”分配的不可能性。)