C++为什么没有虚拟构造函数?
C++为什么没有虚拟构造函数?
听马嘴里说。
来自 Bjarne Stroustrup 的 C++ 风格与技术 FAQ为什么我们没有虚拟构造函数?
虚拟调用是一种在部分信息下完成工作的机制。特别地,“虚拟”允许我们调用一个函数,只需知道任何接口而不需要知道对象的确切类型。要创建一个对象,您需要完全的信息。特别地,您需要知道要创建的对象的确切类型。因此,“对构造函数的调用” 不能是虚拟的。
该 FAQ 条目继续给出了一种实现类似效果的方法,而不需要虚拟构造函数的代码。
虚函数基本上提供了多态行为。也就是说,当您使用一个对象时,它的动态类型与其引用的静态(编译时)类型不同时,它会提供适用于实际对象类型而非对象静态类型的行为。
现在尝试将这种行为应用到构造函数中。当您构造一个对象时,其静态类型始终与实际对象类型相同,因为:
要构造一个对象,构造函数需要该对象的确切类型[...]此外[...]您不能拥有指向构造函数的指针
(Bjarne Stroustup《C++语言程序设计》第424页)
我能想到的两个原因:
技术原因
对象只有在构造函数结束后才存在。为了使用虚表分派构造函数,必须有一个现有的对象,并且有一个指向虚表的指针,但是如果对象仍然不存在,那么如何存在指向虚表的指针呢? :)
逻辑原因
当您想声明一种略微多态的行为时,会使用虚关键字。但是构造函数与多态无关,在C ++中,构造函数的工作只是将对象数据放置在内存中。由于虚表(以及通常的多态性)都涉及多态行为而非多态数据,因此声明虚构造函数没有意义。
摘要: C++标准可能会为"虚构造函数"规定一种符合直觉且对编译器支持不太困难的表示形式和行为,但为什么要针对此特定功能做出标准更改呢,当使用create()
/ clone()
(见下文)已经可以清晰地实现这个功能?与流程管道中的许多其他语言提案相比,它远远不如那么有用。
假设我们有一个"虚构造函数"机制:
Base* p = new Derived(...);
Base* p2 = new p->Base(); // possible syntax???
在上面的代码中,第一行构造了一个Derived
对象,因此*p
的虚函数表可以合理地提供“虚构造函数”以在第二行中使用。(本页上数十个回答声称“对象尚不存在,因此无法进行虚拟构造”的观点过于狭隘,只关注即将构造的对象。)
第二行假定记号new p->Base()
请求动态分配并默认构造另一个Derived
对象。
注意:
编译器必须在调用构造函数之前协调内存分配-构造函数通常支持自动(非正式的"堆栈")分配、静态(用于全局/命名空间作用域和类/函数-静态对象)和动态(非正式的"堆")当使用new
时
要构造p->Base()
中的对象的大小通常无法在编译时得知,因此动态分配是唯一有意义的途径
alloca()
-但会导致显着的低效和复杂性(例如这里和这里)对于动态分配,它必须返回指针,以便稍后进行delete
。
假定的记号明确列出new
以强调动态分配和指针结果类型。
编译器需要:
Derived
需要多少内存,可以通过调用隐式的virtual
sizeof
函数或通过RTTI获得此类信息operator new(size_t)
来分配内存new
调用Derived()
。或者
因此-似乎规定和实现虚构造函数并非难以克服的问题,但是百万美元的问题是:它比使用现有的C++语言特性更好吗...? 就个人而言,我认为下面的解决方案没有任何好处。
C++ FAQ文档记录了一个“虚构造函数”习惯用法,其中包含virtual
create()
和clone()
方法以默认构造或复制构造新的动态分配对象:
class Shape {
public:
virtual ~Shape() { } // A virtual destructor
virtual void draw() = 0; // A pure virtual function
virtual void move() = 0;
// ...
virtual Shape* clone() const = 0; // Uses the copy constructor
virtual Shape* create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
public:
Circle* clone() const; // Covariant Return Types; see below
Circle* create() const; // Covariant Return Types; see below
// ...
};
Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }
也可以更改或重载create()
以接受参数,但为了匹配基类/接口的virtual
函数签名,重载的参数必须完全匹配其中一个基类重载。有了这些明确的用户提供的功能,很容易添加日志记录、仪器调节、修改内存分配等。
clone
和create
不能直接与标准容器一起使用,但编写一个小的管理类型从复制构造函数等处进行clone
非常简单(例如,请参见此处)。这样的管理对象也可以通过值传递,如果您发现使用引用更容易。使用clone
/ create
,将private
和管理对象设置为“friend”,可以确保一致的使用。尽管如此,这确实是一种额外的复杂性,可能会使新的C ++程序员感到沮丧... - Tony Delroyoperator<
。此外,由于它不是语言的一部分,因此将使用这种东西的代码与不使用它的代码进行互操作将非常困难。 - David Schwartz我们有这个功能,只不过它不是一个构造函数 :-)
struct A {
virtual ~A() {}
virtual A * Clone() { return new A; }
};
struct B : public A {
virtual A * Clone() { return new B; }
};
int main() {
A * a1 = new B;
A * a2 = a1->Clone(); // virtual construction
delete a2;
delete a1;
}
除了语义上的原因,直到对象被构造后才会有vtable,因此虚拟指定是无用的。
typeid(*this).name()
来查看它。 - curiousguyvirtual
关键字。它只有在对象存在时才会起作用。而构造函数被用于创建对象。构造函数将在对象创建时被调用。virtual
,根据虚拟关键字的定义,它应该有现有的对象来使用,但是构造函数用于创建对象,所以这种情况永远不会存在。因此,你不应该使用构造函数作为虚函数。Constructors cannot be declared virtual
虚函数被用来根据指针所指向的对象类型调用函数,而不是根据指针本身的类型调用函数。但是构造函数并没有被“调用”,它只在对象被声明时调用一次。因此,在C++中,构造函数不能被声明为虚函数。