为什么我们在C ++中没有虚拟构造函数?

时间:2009-04-09 08:46:31

标签: c++ constructor virtual-functions

为什么C ++没有虚拟构造函数?

23 个答案:

答案 0 :(得分:218)

从马的嘴里听到:)。

来自Bjarne Stroustrup的C ++风格和技巧常见问题解答 Why don't we have virtual constructors?

  

虚拟调用是一种部分完成工作的机制   信息。特别是,“虚拟”允许我们调用一个函数   只知道任何接口而不知道对象的确切类型。至   创建一个您需要完整信息的对象。特别是你   需要知道你想要创建的确切类型。所以,   “对构造函数的调用”不能是虚拟的。

FAQ条目继续给出代码,以便在没有虚构造函数的情况下实现此目的。

答案 1 :(得分:121)

虚函数基本上提供了多态行为。也就是说,当您使用动态类型与引用它的静态(编译时)类型不同的对象时,它提供的行为适用于实际类型的对象而不是对象的静态类型。

现在尝试将这种行为应用于构造函数。构造对象时,静态类型始终与实际对象类型相同,因为:

  

要构造一个对象,构造函数需要它创建的对象的确切类型[...]此外[...]你不能有一个指向构造函数的指针

(Bjarne Stroustup(P424 The C ++ Programming Language SE))

答案 2 :(得分:57)

与面向对象语言(如Smalltalk或Python)不同,其中构造函数是表示类的对象的虚方法(这意味着您不需要GoF abstract factory pattern,因为您可以传递表示该对象的对象C ++是一种基于类的语言,并没有表示任何语言结构的对象。该类在运行时不作为对象存在,因此您无法在其上调用虚方法。

这符合“你不为你不使用的东西付费”的理念,尽管我见过的每个大型C ++项目都最终实现了某种形式的抽象工厂或反思。

答案 3 :(得分:39)

我能想到的两个原因:

技术原因

该对象仅在构造函数结束后才存在。为了使用虚拟表调度构造函数,必须有一个带有指向虚拟表的指针的现有对象,但是如何存在指向虚拟表的指针如果对象仍然不存在? :)

逻辑推理

如果要声明某种多态行为,请使用virtual关键字。但是构造函数没有任何多态性,C ++中的构造函数就是简单地将对象数据放在内存中。由于虚拟表(以及一般的多态)都是关于多态行为而不是多态数据,因此声明虚拟构造函数是没有意义的。

答案 4 :(得分:13)

我们这样做,它不是构造函数: - )

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;
}

答案 5 :(得分:13)

除了语义原因之外,在构造对象之前没有vtable,因此虚拟指定无用。

答案 6 :(得分:12)

摘要 :C ++标准 可以为"虚拟构造函数指定一个符号和行为" s'相当直观,并且对于编译器来说并不太难以支持,但是为什么在使用create() / clone()已经可以干净地实现功能时专门对此进行标准更改(请参阅下面)?它不像管道中的许多其他语言提案那样有用。

讨论

让我们假设一个"虚拟构造函数"机构:

Base* p = new Derived(...);
Base* p2 = new p->Base();  // possible syntax???

在上面,第一行构造了一个Derived对象,因此*p的虚拟调度表可以合理地提供一个"虚拟构造函数"用于第二行。 (此页面上的数十个答案说明"对象尚未存在,因此无法进行虚拟构建" 不必要地将重点放在要构建的对象上。)

第二行假设符号new p->Base()请求动态分配和另一个Derived对象的默认构造。

注意:

  • 编译器必须在调用构造函数之前协调内存分配 - 构造函数通常支持自动(非正式地"堆栈&# 34;)分配,静态(对于全局/命名空间范围和类 - /函数 - static对象)和动态(非正式地"堆&# 34;)何时使用new

    • p->Base()要构造的对象的大小通常在编译时是已知的,因此 动态分配是唯一有意义的方法

  • 对于动态分配,它必须返回一个指针,以便以后可以delete d。

  • 假定的表示法明确列出new以强调动态分配和指针结果类型。

编译器需要:

  • 通过调用隐式Derived virtual函数或通过RTTI获取此类信息,找出所需的内存sizeof
  • 致电operator new(size_t)分配内存
  • 使用展示位置Derived()调用new

OR

  • 为结合动态分配和构造的函数创建额外的vtable条目

所以 - 指定和实现虚拟构造函数似乎并不是不可克服的,但是价值数百万美元的问题是:它如何比使用现有C ++语言特性更好??就个人而言, 我认为下面的解决方案没有任何好处。

`clone()`和`create()`

C++ FAQ documents a "virtual constructor" idiom,包含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函数签名,覆盖的参数必须与一个完全匹配基类重载。使用这些明确的用户提供的工具,可以轻松添加日志记录,检测,更改内存分配等。

答案 7 :(得分:5)

虽然虚拟构造函数的概念并不适合,因为对象类型是创建对象的先决条件,但它并不完全过度。

GOF的“工厂方法”设计模式利用虚拟构造函数的“概念”,在某些设计情况下可以轻松使用。

答案 8 :(得分:5)

您可以在@stefan的答案中找到一个示例和技术原因,以解释为什么不允许这样做。现在根据我对这个问题的合理答案是:

虚拟关键字的主要用途是在我们不知道基类指针指向的对象类型时启用多态行为。

但是想想这是更原始的方式,对于使用虚拟功能,你需要一个指针。指针需要什么?一个指向的对象! (考虑正确执行程序的情况)

因此,我们基本上需要一个已存在于内存中的对象(我们不关心如何分配内存,可能是在编译时或运行时),以便我们的指针可以正确指向该对象。

现在,想想当被指向的类的对象被分配一些内存时的情况 - >它的构造函数将在该实例自动调用!

所以我们可以看到我们实际上并不需要担心构造函数是虚拟的,因为在任何情况下你都希望使用多态行为,我们的构造函数就已经被执行,使我们的对象可以使用了!

答案 9 :(得分:5)

C ++中的虚函数是运行时多态的实现,它们将执行函数重写。通常,当您需要动态行为时,{C}中会使用virtual关键字。它仅在对象存在时才有效。而构造函数用于创建对象。在创建对象时将调用构造函数。

因此,如果您将构造函数创建为virtual,则根据虚拟关键字定义,它应该具有要使用的现有对象,但构造函数用于创建对象,因此这种情况永远不会存在。所以你不应该把构造函数用作虚拟。

因此,如果我们尝试声明虚构造函数编译器抛出一个错误:

  

无法将构造函数声明为虚拟

答案 10 :(得分:4)

虚函数用于根据指针指向的对象类型调用函数,而不是指针本身的类型。但是没有“调用”构造函数。声明对象时只调用一次。因此,构造函数不能在C ++中变为虚拟。

答案 11 :(得分:3)

当人们提出这样的问题时,我喜欢自言自语“如果真的有可能会发生什么?”我真的不知道这意味着什么,但我想这与能够根据正在创建的对象的动态类型覆盖构造函数实现有关。

我发现了很多潜在的问题。首先,在调用虚拟构造函数时,派生类不会完全构造,因此实现可能存在问题。

其次,在多重继承的情况下会发生什么?你的虚拟构造函数可能会被多次调用,然后你需要知道哪一个被调用。

第三,一般来说,在构造时,对象没有完全构造虚拟表,这意味着它需要对语言规范进行大的更改,以允许对象的动态类型将是在施工时知道。这将允许基类构造函数在构造时调用其他虚函数,而不是完全构造的动态类类型。

最后,正如其他人指出的那样,您可以使用静态“创建”或“初始化”类型函数来实现一种虚拟构造函数,这些函数基本上与虚构造函数一样。

答案 12 :(得分:3)

您也不应该在构造函数中调用虚函数。请参阅:http://www.artima.com/cppsource/nevercall.html

另外我不确定你真的需要一个虚拟构造函数。没有它你可以实现多态结构:你可以编写一个函数来根据所需的参数构造你的对象。

答案 13 :(得分:3)

为具有一个或多个“虚函数”的每个类创建虚拟表(vtable)。每当创建一个这样的类的对象时,它包含一个“虚指针”,它指向相应的vtable的基础。每当有虚函数调用时,vtable用于解析函数地址。     构造函数不能是虚拟的,因为当执行类的构造函数时,内存中没有vtable,意味着还没有定义虚拟指针。因此构造函数应该始终是非虚拟的。

答案 14 :(得分:2)

我们只是简单地说它就像..我们不能继承构造函数。因此,没有必要将它们声明为虚拟,因为虚拟提供了多态性。

答案 15 :(得分:2)

面试答案是:虚拟ptr和表与对象相关,但与类无关。因此构造函数将构建虚拟表 因此,我们无法使用虚拟构造函数,因为在创建obj之前没有Vtable。

答案 16 :(得分:2)

只有在具有指向派生类对象的基类指针时,虚拟机制才有效。构造有自己的基类构造函数调用规则,基本上是派生的基类。虚拟构造函数如何有用或被调用?我不知道其他语言是做什么的,但我看不出虚拟构造函数如何有用甚至实现。构造需要为虚拟机制提供任何意义,并且还需要为已经创建的vtable结构进行构造,这提供了多态行为的机制。

答案 17 :(得分:1)

有一个非常基本的原因:构造函数是有效的静态函数,而在C ++中,没有静态函数可以是虚函数。

如果您对C ++有很多经验,那么您就会知道静态和放大之间的区别。成员职能。静态函数与CLASS相关联,而不是与对象(实例)相关联,因此它们不会看到“this”指针。只有成员函数才能是虚拟的,因为vtable-隐藏的函数指针表使“虚拟”工作 - 实际上是每个对象的数据成员。

现在,构造函数的工作是什么?它在名称中 - “T”构造函数在分配T对象时初始化它们。这会自动排除它成为会员功能!在具有“this”指针并因此具有vtable之前,对象必须是EXIST。这意味着,即使语言将构造函数视为普通函数(由于相关原因我不会进入),它们也必须是静态成员函数。

看到这个的好方法是查看“工厂”模式,尤其是工厂功能。他们做你想做的事,你会注意到如果T级有工厂方法,它总是静止的。必须是。

答案 18 :(得分:1)

C ++虚拟构造函数是不可能的。例如,您不能将构造函数标记为虚拟。请尝试此代码

#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(); 
}

在main a=new anotherClass;中,在anotherClass的指针a中为aClass分配一个内存。这会导致构造函数(In aClassanotherClass)自动调用。因此我们不需要将构造函数标记为虚拟。因为当创建对象时,它必须遵循创建链(即首先是基础,然后是派生类)。 但是当我们尝试删除delete a;时,它只会调用基础析构函数。所以我们必须使用virtual关键字来处理析构函数。 所以虚拟构造函数是不可能的,但虚析构函数是。谢谢

答案 19 :(得分:1)

“构造函数不能是虚拟的”

  • 有一些正当的理由可以证明这种说法是正确的。
  1. 要创建对象,对象类的构造函数必须与类具有相同的类型。但是,这对于虚拟实现的构造函数是不可能的。
  2. 在调用构造函数时,不会创建virtual table来解析任何虚函数调用。因此,虚拟构造函数本身将无处可寻。

因此,不可能将构造函数声明为虚拟构造函数。

答案 20 :(得分:0)

如果您从逻辑上思考构造函数的工作方式以及虚函数在C ++中的含义/用法,那么您将会发现虚拟构造函数在C ++中毫无意义。在C ++中声明虚拟内容意味着它可以被当前类的子类覆盖,但是当创建objected时调用构造函数,那时你不能创建类的子类,你必须是创建类,因此永远不需要声明构造函数虚拟。

另一个原因是,构造函数与其类名具有相同的名称,如果我们将构造函数声明为virtual,那么它应该在其派生类中重新定义,并且名称相同,但是不能使用相同的名称。类。所以不可能拥有虚拟构造函数。

答案 21 :(得分:0)

  1. 当调用构造函数时,尽管到那时为止都没有创建对象,但是我们仍然知道将要创建的对象的类型,因为该类的特定构造函数该对象所属的对象已被调用。

    与功能关联的

    Virtual关键字表示将调用特定对象类型的功能

    因此,我的想法是,无需创建虚拟构造函数,因为已经调用了将要创建其对象的所需构造函数,而将构造函数虚拟化只是多余的事情,因为 object-特定的构造函数已经被调用,这与调用特定于类的函数相同,后者是通过virtual关键字实现的。

    尽管出于vptr和vtable相关原因,内部实现不允许虚拟构造函数。


  1. 另一个原因是C ++是一种静态类型的语言,我们需要在编译时知道变量的类型。

    编译器必须知道创建对象的类类型。要创建的对象的类型是编译时的决定。

    如果我们将构造函数设为虚拟,则意味着我们不需要在编译时知道对象的类型(这就是虚函数提供的功能。我们不需要知道实际的对象,只需要知道指向实际对象的基本指针会在不知道对象类型的情况下调用该对象的虚函数),如果我们在编译时不知道对象的类型,则它与静态类型的语言相反。因此,无法实现运行时多态。

    因此,在编译时不知道对象的类型就不会调用Constructor。因此创建虚拟构造函数的想法失败了。

答案 22 :(得分:-1)

Vpointer是在创建对象时创建的。在创建对象之前,vpointer不会存在。所以没有必要将构造函数设置为虚拟。