调用实现前的虚函数

3

所以我有这样的设置

class Base
{
public:
  Base();
  virtual void parse() = 0;
};

Base::Base()
{
  parse();
} 

class Sub : public Base
{
public:
  Sub();
  void parse();
};

Sub::Sub() : Base() {}

void Sub::parse()
{
  // DO stuff
}

我想知道是否有一种类似于这样的方法可以实现,目前我收到了一个错误,说我不能调用纯虚函数,这很合理。是否有任何关键字可以让这个工作?
我认为只将parse()定义为虚函数而不是纯虚函数就可以解决问题,但我希望用户必须重写它。
4个回答

10

在构造函数(或析构函数)中调用虚成员函数永远不会让你进入派生类中重写的那个版本

原因是基类构造函数(析构函数)在派生类构造函数(析构函数)之前(之后)执行。这意味着表示派生类的部分对象根本不存在(不再存在)。而在不存在的对象上调用成员函数是不可能的。

为了实现你想要的功能,你需要实现一种两阶段构造的形式(这不是语言内置的功能)。通常,这可通过先让一个包装类完全构建一个 Sub 对象,然后再对其调用 parse() 来实现。


1
简而言之,在任何类的构造函数中,this指向同一个类,无论它从哪里调用。+1 - Alok Save
为什么不在你的回答中使用我的链接呢?http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6 - Matt K
@Als:虽然他说的话是正确的,但对于新手来说也很难理解。:) - sbi
@mkb:在你的回答出现之前,我已经开始用自己的话解释了。不过,我很喜欢你添加链接的做法,这样遇到这个问题的人可以跟进去看看。从不同的角度解释事情总是好的。 - sbi
确实,即使许多经验丰富的程序员也会陷入这个陷阱,因此我添加了易于记忆的规则。你的解释更加恰当地解释了它 :) - Alok Save
@mkb:工厂方法确实是个好主意(可见性+1)。而且它不一定要是静态类方法。任何常见的工厂可能性都可以。 - sbi

2
虚函数的解析无法在尚未构造的类中找到函数。(正式来说:对象的动态类型是正在运行的构造函数或析构函数的类型。)因此,您需要某种后构造函数,但语言不支持它。
您可以通过使用虚函数的一个虚参并在其析构函数中调用所需的函数来解决这个问题,例如:
class Base
{
public:
    class PostConstructor
    {
        Base* owner;
        friend class Base;
    public:
        PostConstructor() : owner( NULL ) {}
        ~PostConstructor() { owner->parse(); }
    };
    Base( PostConstructor const& helper )
    {
        helper.owner = this;
    }
};

class Derived : public Base
{
public:
    Derived( PostConstructor const& helper = PostConstructor() )
        : Base( helper )
    {
    }
};

或者如果这个类有参数:

class Base
{
    std::string name;
public:
    class PostConstructor
    {
        Base* owner;
        std::string arg;    //  for example...
        friend class Base;
    public:
        PostConstructor( std::string const& arg )  // implicit conversion!!
                : owner( NULL ), arg( arg ) {}
        ~PostConstructor() { owner->parse(); }
        operator std::string() const { return arg; }
    };
    Base( PostConstructor const& helper )
        : name( helper )
    {
        helper.owner = this;
    }
};

class Derived : public Base
{
public:
    Derived( PostConstructor const& helper )
        : Base( helper )
    {
    }
};

这个方法可行是因为PostConstructor将会是一个临时的对象,在整个表达式结束后被销毁(当Derived完全构造完成)。

在两种情况下,派生类必须相互配合(如果它们不配合,则无法编译)。但是,如果类在更复杂的表达式中间构造,这种技巧也可能失败。例如:

Derived(...).someFunction();

在这种情况下,完整表达式的结尾将在从someFunction返回之后,这可能有点晚来调用parse()
尽管存在局限性,但我发现在某些场合下这很有用。

2
建设性的批评。您的回答和“解决方法”是正确的,但对于非专业程序员来说似乎过于复杂。 - umlcat
@umlcat 这种解决方法确实有一些缺点,并不是万能的解决方案。当看到它时,我希望任何C++程序员都可以理解(只要在发布的文本部分注明了我放置的内容)。然而,仍在学习C++的人可能会遇到问题。 - James Kanze

2

这里的重点是你不能在构造函数中调用纯虚函数,即使你提供了实现,也只会使用基类的实现。

原因很简单,Base的构造函数在Sub的开始时执行,因此对Sub的任何虚函数调用都将在不完整的对象上调用。

一般来说,没有解决方案:你不能在构造函数或析构函数中分派虚函数。


0

抽象或纯虚方法是虚拟方法的特殊情况(所有抽象或纯虚方法都是虚拟的)。

我的先前答案是错误的,因为我忽略了构造函数。在C++中,构造函数不是虚拟的,并且不允许在构造函数中调用虚拟(抽象和非抽象方法)。如果您从另一个不是构造函数的方法中调用非抽象重写的“parse”,那就没问题了。

问题不在于该方法是抽象的,而在于它是从构造函数中调用的。

#include <conio>

class Base
{
public:
  // a constructor:
  Base();

  // "virtual and abstract" method:
  virtual void parse() = 0;

  // "virtual non abstract" method:
  virtual void hello();
};

// Error: you cannot call a virtual method from a constructor,
// wheter is abstract or not:
Base::Base()
{
  // error:
  parse();

  // error:
  hello();
} 

Base::hello()
{
  cout << "Hello World\n";
} 

class Sub : public Base
{
public:
  Sub();

  // forgot "virtual" here,
  // other languages use "override" instead, here:
  virtual void parse();
  // another "overriden" methods:
  virtual void parse();
};

// right: its important to call the base constructor,
// in this case:
Sub::Sub() : Base()
{
  // ...
}

void Sub::parse()
{
  // DO stuff
}

int main()
{
  Base *MyBaseObject = new Base();
  MyObject->parse();    

  Sub *MyObject = new Sub();
  MyObject->parse();

  return 0;
}

这个问题有一个解决方法。为了调用一个虚方法,就像从构造函数中调用一样,需要声明一个新的方法,在构造函数之后立即调用它:

#include <conio>

class Base
{
public:
  // a constructor:
  Base();

  // a "postconstructor" or "pseudoconstructor"
  virtual void create();

  // "virtual and abstract" method:
  virtual void parse() = 0;

  // "virtual non abstract" method:
  virtual void hello();
};

// Error: you cannot call a virtual method from a constructor,
// wheter is abstract or not:
Base::Base()
{
  // no virtual methods called here,
  // wheter abstract or not
} 

// its not a real constructor, just a virtual method:
void Sub::create()
{
  // ...
}   
Base::hello()
{
  cout << "Hello World\n";
} 

class Sub : public Base
{
public:
  Sub();

  virtual void create();

  // forgot "virtual" here,
  // other languages use "override" instead, here:
  virtual void parse();
  // another "overriden" methods:
  virtual void parse();
};

// right: its important to call the base constructor,
// in this case:
Sub::Sub() : Base()
{
  // ...
}

// its not a real constructor, just a virtual method:
void Sub::create() : create()
{
  parse();
}

void Sub::parse()
{
  // DO stuff
}

int main()
{
  // this commented code, wont work
  /*
  Base *MyBaseObject = new Base();
  MyObject->create();    
  MyObject->parse();    
  */

  // calling "pseudo-constructor",
  // just after real constructor
  Sub *MyObject = new Sub(); MyObject->create();
    MyObject->parse();

  return 0;
}

我的错误,抱歉。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接