已经看过一些相关的问题,但不是这个问题......
我把课程视为适合几个主要类别,让我们说这四个是为了简单:
值类,它包含一些数据和一堆操作。可以复制它们并进行有意义的比较以获得相等性(通过==
预期副本相等)。 这些几乎总是缺乏虚拟方法。
唯一类,其实例具有您禁用分配和复制的标识。这些通常没有operator==
,因为您将它们作为指针进行比较,而不是作为对象进行比较。 这些通常有很多虚拟方法,因为没有object-slicing的风险,因为你被迫通过指针或引用传递它们。
唯一但可克隆的类可禁用复制,但预先设计为支持克隆,如果这是您真正想要的。 这些都有虚拟方法,最重要的是那些遵循virtual construction / cloning idiom
容器类,它继承了所持有的任何属性。 这些往往没有虚拟方法......例如参见"Why don't STL containers have virtual destructors?"。
无论是否持有这种非正式的信仰体系,我都尝试过将虚拟方法添加到可复制的内容中。虽然我可能认为如果“有效”会“真的很酷”,但它不可避免地会破裂。
这让我想知道是否有人有一个具有虚拟方法并且不禁用复制的类型的实际好例子?
答案 0 :(得分:7)
我所拥有的唯一反例是要分配堆栈而不是堆分配的类。我用它的一个方案是依赖注入:
class LoggerInterface { public: virtual void log() = 0; };
class FileLogger final: public LoggerInterface { ... };
int main() {
FileLogger logger("log.txt");
callMethod(logger, ...);
}
这里的关键点是final
关键字,这意味着复制FileLogger
无法导致对象切片。
但是,可能只是final
将FileLogger
转换为值类。
注意:我知道,复制记录器似乎很奇怪......
答案 1 :(得分:6)
能够复制多态类没有任何内在错误。问题是能够复制非叶类。对象切片可以帮到你。
遵循的一个好的经验法则是永远不会从具体的类派生出来。这样,非叶类自动是不可实例化的,因此是不可复制的。但是,为了安全起见,禁用分配也不会有什么坏处。
当然,通过虚函数复制对象没有任何问题。这种复制是安全的。
多态类通常不是“值类”,但它确实发生了。我想起了std::stringstream
。它不是可复制的,但它是可移动的(在C ++ 11中)并且移动与关于切片的复制没有区别。
答案 2 :(得分:3)
虚拟调度发生在运行时。人们应该想要的唯一原因是在运行时才能知道对象的实际动态类型。如果您在编写程序时已经知道所需的动态类型,则可以使用不同的非虚拟技术(例如模板或非多态继承)来构建代码。
需要运行时输入的一个很好的例子是解析I / O消息或处理事件 - 任何一种情况,你可以通过某种方式使用某种大的切换表来选择正确的混凝土类型,或者您编写自己的注册和调度系统,它基本上重新发明多态,或者您只是使用虚拟调度。
(让我插一个警告:许多人滥用虚拟功能来解决不需要它们的问题,因为它们不是动态的。要小心,并批评什么你看。)
有了这个说法,现在很明显,你的代码将主要处理多态 base 类,例如:在函数接口或容器中。因此,让我们重新解释一个问题:这样的基类是否应该是可复制的?好吧,因为你从来没有实际的,派生最多的基础对象(即基类本质上是抽象的),所以这不是一个问题,并且不需要这个。你已经提到了克隆"克隆"成语,这是在多态中复制的适当类比。
现在,"克隆"函数必须在每个叶类中实现,并且它必然需要复制叶类。所以,是的,可克隆层次结构中的每个叶类都是一个具有虚函数和复制构造函数的类。由于派生类的复制构造函数需要复制其基础子对象,因此所有基础也需要是可复制的。
所以,现在我相信我们已将问题提炼到两种可能的情况:您的类层次结构中的所有内容都是完全不可复制的,或者您的层次结构支持克隆,因此必然< em>其中的每个类都是可复制的。
具有虚函数的类是否应该具有复制构造函数?绝对。 (这回答了您的原始问题:当您将类集成到可克隆的多态层次结构中时,您可以向其添加虚函数。)
您是否应该尝试从基准参考制作副本?可能不是。
答案 3 :(得分:2)
不是单一的,而是有两个类:
#include <iostream>
#include <vector>
#include <stdexcept>
class Polymorph
{
protected:
class Implementation {
public:
virtual ~Implementation() {};
// Postcondition: The result is allocated with new.
// This base class throws std::logic error.
virtual Implementation* duplicate() {
throw std::logic_error("Duplication not supported.");
}
public:
virtual const char* name() = 0;
};
// Precondition: self is allocated with new.
Polymorph(Implementation* self)
: m_self(self)
{}
public:
Polymorph(const Polymorph& other)
: m_self(other.m_self->duplicate())
{}
~Polymorph() {
delete m_self;
}
Polymorph& operator = (Polymorph other) {
swap(other);
return *this;
}
void swap(Polymorph& other) {
std::swap(m_self, other.m_self);
}
const char* name() { return m_self->name(); }
private:
Implementation* m_self;
};
class A : public Polymorph
{
protected:
class Implementation : public Polymorph::Implementation
{
protected:
Implementation* duplicate() {
return new Implementation(*this);
}
public:
const char* name() { return "A"; }
};
public:
A()
: Polymorph(new Implementation())
{}
};
class B : public Polymorph {
protected:
class Implementation : public Polymorph::Implementation {
protected:
Implementation* duplicate() {
return new Implementation(*this);
}
public:
const char* name() { return "B"; }
};
public:
B()
: Polymorph(new Implementation())
{}
};
int main() {
std::vector<Polymorph> data;
data.push_back(A());
data.push_back(B());
for(auto x: data)
std::cout << x.name() << std::endl;
return 0;
}
注意:在此示例中,始终复制对象(但您可以实现共享语义)