为什么我们在C ++中没有虚拟构造函数?
为什么C ++没有虚拟构造函数?
从马的嘴里听到:)。
来自Bjarne Stroustrup的C ++风格和技巧常见问题为什么我们没有虚拟构造函数?
虚拟呼叫是一种部分信息获取工作的机制。 特别是,“虚拟”允许我们调用一个函数,只知道任何接口而不知道对象的确切types。 要创build一个对象,你需要完整的信息。 特别是,您需要知道您想要创build的确切types。 因此,“调用构造函数”不能是虚拟的。
常见问题解答条目继续给代码一个方法来实现这一目的,而不需要一个虚拟的构造函数。
虚函数基本上提供多态行为。 也就是说,当您使用dynamictypes与引用它的静态(编译时)types不同的对象时,它提供的行为适合实际types的对象,而不是对象的静态types。
现在尝试将这种行为应用于构造函数。 当你构造一个对象时,静态types总是和实际的对象types一样:
要构造一个对象,构造函数需要它创build的对象的确切types[…]此外,您不能有一个指向构造函数的指针
(Bjarne Stroustup(P424 The C ++ Programming Language SE))
与面向对象的语言(如Smalltalk或Python)不同,构造函数是代表类的对象的虚拟方法(这意味着您不需要GoF 抽象工厂模式 ,因为您可以传递代表类的对象而不是你自己),C ++是一个基于类的语言,并没有对象表示任何语言的结构。 该类在运行时不作为对象存在,所以不能在其上调用虚拟方法。
这符合'你不支付你不使用'的哲学,尽pipe我见过的每个大型C ++项目都已经实现了某种forms的抽象工厂或反思。
我能想到的两个理由:
技术原因
该对象只在构造函数结束后才存在。为了使用虚拟表来分派构造函数,必须有一个存在对象的指针指向虚拟表,但是如果对象存在,则指向虚拟表的指针如何存在仍然不存在? 🙂
逻辑的原因
当你想声明一个有点多态的行为时使用virtual关键字。 但是没有什么多态的构造函数,C ++中的构造函数的工作就是简单地将对象数据放在内存中。 由于虚拟表(通常是多态)都是关于多态的行为,而不是关于多态的数据,所以声明虚拟构造函数是没有意义的。
我们这样做,它只是不是一个构造函数:-)
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,从而使得虚拟名称无用。
简介 :C ++标准可以为虚拟构造函数指定符号和行为,这对编译器来说是相当直观和不难的,但是为什么当这个function已经可以用create()
清楚地实现时, / clone()
(见下文)? 这还没有其他语言提案那么有用。
讨论
假设一个“虚拟构造函数”机制:
Base* p = new Derived(...); Base* p2 = new p->Base(); // possible syntax???
在上面的第一行构造一个Derived
对象,所以*p
的虚拟调度表可以合理的提供一个“虚拟构造器”供第二行使用。 (在这个页面上有几十个答案,说明“这个对象还没有存在,所以虚拟build构是不可能的”,这是不必要地过分关注于待构build的对象的。)
第二行假定符号new p->Base()
来请求另一个Derived
对象的dynamic分配和默认构造。
笔记:
-
编译器必须在调用构造函数之前编排内存分配 – 构造函数通常支持自动 (非正式“堆栈”)分配, 静态 (对于全局/命名空间范围和类/
static
对象)和dynamic (非正式“堆”用来-
由
p->Base()
构造的对象的大小在编译时通常是不可知的,所以dynamic分配是唯一有意义的方法- 有可能在堆栈上分配运行时指定的内存量 – 例如GCC的可变长度数组扩展
alloca()
– 但会导致显着的低效率和复杂性(例如分别在这里和这里 )
- 有可能在堆栈上分配运行时指定的内存量 – 例如GCC的可变长度数组扩展
-
-
对于dynamic分配,它必须返回一个指针,以便以后可以
delete
内存。 -
假定的符号明确地列出
new
的强调dynamic分配和指针结果types。
编译器需要:
- 通过调用隐式
virtual
函数sizeof
函数或通过RTTI提供这样的信息来找出Derived
所需的内存量 - 调用
operator new(size_t)
来分配内存 - 用位置
new
调用Derived()
。
要么
- 为一个结合了dynamic分配和构造的函数创build一个额外的vtable条目
因此,指定和实现虚拟构造函数似乎并不是难以逾越的,但是这个数百万美元的问题是:如何使用现有的C ++语言function来实现? 就我个人而言, 我看不出以下解决scheme的好处。
clone()和create()
C ++ FAQlogging了一个“虚构构造器”的成语 ,它包含了virtual
create()
和clone()
方法来默认构造或复制一个新的dynamic分配的对象:
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
函数签名,重写的参数必须与基类重载之一完全匹配。 通过这些显式的用户提供的工具,可以很容易地添加日志logging,工具,更改内存分配等。
虽然虚拟构造函数的概念并不适合,因为对象types是创build对象的先决条件,但它并没有完全超越规则。
GOF的“工厂方法”devise模式利用了虚拟构造器的“概念”,这在某些devise情况下是很方便的。
虚函数用于根据指针指向的对象的types来调用函数,而不是指针本身的types。 但是构造函数不是“被调用的”。 当一个对象被声明时它只被调用一次。 所以,构造函数不能在C ++中变成虚拟的。
你不应该在你的构造函数中调用虚函数。 请参阅: http : //www.artima.com/cppsource/nevercall.html
另外我不确定你真的需要一个虚拟的构造函数。 没有它,你可以实现多态的构造:你可以编写一个函数,根据所需的参数构造你的对象。
当人们提出这样的问题时,我喜欢自言自语:“如果这实际上是可能的,会发生什么?” 我真的不知道这将意味着什么,但我想这可以根据所创build的对象的dynamictypes来覆盖构造函数实现。
我看到了一些这方面的潜在问题。 首先,派生类在虚拟构造函数被调用的时候不会被完全构造,所以在实现中存在潜在的问题。
其次,在多inheritance的情况下会发生什么? 假设你的虚拟构造函数被多次调用,那么你需要知道哪一个被调用。
第三,一般来说在施工时,对象没有完全构build虚拟表,这意味着需要对语言规范进行大的改变,以允许在施工时知道对象的dynamictypes时间。 这将允许基类构造函数可能在构造时调用其他的虚函数,并且没有完全构造dynamic类types。
最后,正如别人指出的,你可以使用静态的“创build”或“初始化”types的函数来实现一种虚拟构造函数,这些函数基本上和虚拟构造函数做同样的事情。
虚拟机制只在基类指向派生类对象时才起作用。 构造函数对于调用基类构造函数有自己的规则,基本上就是派生类的基类。 虚拟构造函数怎么可能有用或被调用? 我不知道其他语言是做什么的,但是我不明白虚拟构造函数是如何有用甚至实现的。 对于虚拟机制来说,需要build立一个有意义的构造,并且还需要为已经创build的vtable结构进行构造,从而提供多态行为的机制。
我们不能简单地说它就像..我们不能inheritance构造函数。 所以没有必要宣布它们是虚拟的,因为虚拟提供了多态性。
C ++中的虚函数是一个运行时多态的实现,它们将会覆盖函数。 一般来说,当你需要dynamic行为的时候, virtual
关键字用在C ++中。 只有当对象存在时它才会起作用。 而构造函数用于创build对象。 构造函数将在创build对象时被调用。
因此,如果按照虚拟关键字定义创build构造函数为virtual
,则应该使用现有的对象,但是使用构造函数来创build对象,因此这种情况将永远不会存在。 所以你不应该使用构造函数作为虚拟的。
所以,如果我们试图声明虚构造器编译器抛出一个Error:
构造函数不能被声明为虚拟的
为每个具有一个或多个“虚拟function”的类制作虚拟表(vtable)。 每当一个Object创build这样的类时,它就包含一个指向相应的vtable的基础的“虚拟指针”。 每当有虚函数调用时,vtable就被用来parsing函数地址。 构造函数不能是虚拟的,因为当一个类的构造函数被执行时,内存中没有vtable,意味着没有定义虚拟指针。 因此,构造函数应该始终是非虚拟的。
C ++虚拟构造函数是不可能的。例如,你不能将一个构造函数标记为virtual.Try这个代码
#include<iostream.h> using namespace std; class aClass { public: virtual aClass() { } }; int main() { aClass a; }
它会导致一个错误。这段代码试图声明一个构造函数为虚拟的。 现在让我们试着了解为什么我们使用虚拟关键字。 虚拟关键字用于提供运行时多态性。 例如试试这个代码。
#include<iostream.h> using namespace std; class aClass { public: aClass() { cout<<"aClass contructor\n"; } ~aClass() { cout<<"aClass destructor\n"; } }; class anotherClass:public aClass { public: anotherClass() { cout<<"anotherClass Constructor\n"; } ~anotherClass() { cout<<"anotherClass destructor\n"; } }; int main() { aClass* a; a=new anotherClass; delete a; getchar(); }
在主a=new anotherClass;
在声明为aClass
types的指针中为anotherClass
分配一个内存。这将导致构造函数(在aClass
和anotherClass
)自动调用。因此,我们不需要将构造函数标记为虚函数。因为当创build一个对象时,它必须遵循创造的链条(即先是基础,然后是派生类)。 但是,当我们尝试删除一个delete a;
它会导致只调用基本的析构函数。所以我们必须使用virtual关键字来处理析构函数。 所以虚拟构造函数是不可能的,但虚拟析构函数是。谢谢
有一个非常基本的原因:构造函数是有效的静态函数,而在C ++中,没有静态函数可以是虚拟的。
如果你有很多关于C ++的经验,你完全了解静态和成员函数的区别。 静态函数与CLASS相关联,而不是对象(实例),所以它们看不到“this”指针。 只有成员函数可以是虚拟的,因为vtable-使得“虚拟”function指针的隐藏表是真正的每个对象的数据成员。
那么,施工人员的工作是什么? 它是在名称中 – “T”构造函数初始化T对象,因为它们被分配。 这会自动排除它是一个成员函数! 一个对象必须先有一个“this”指针,然后才是一个vtable。 这意味着,即使语言把构造函数当作普通函数处理(它不会,因为相关的原因我不会进入),它们必须是静态成员函数。
一个很好的方法是看“工厂”模式,尤其是工厂function。 他们做你以后的事情,你会注意到,如果T类有一个工厂方法,它总是静态的。 它一定要是。
Vpointer是在创build对象时创build的。 vpointer在对象创build之前不存在。 所以没有必要把构造器变成虚拟的。