构造函数用例中的虚函数调用

virtual function call in constructor use case

本文关键字:函数调用 构造函数      更新时间:2023-10-16

所以我从各种在线资源中知道,从构造函数中调用虚函数通常是不行的。 我意识到这里的问题是基类将首先构造,C++将首先调用函数的基类版本。 但是,我有一个独特的用例,可能可以接受。 我将不胜感激一些评论。 考虑这种情况。

class Base
{
public:
    Base(string data)
    {
        Parse(data);
    }
    ~Base(){}
private:
    virtual Parse(string data);
}
class Derived : public Base
{
public:
    Derived(string data)
    {
        Parse(data);
    }
    ~Derived();
private:
    Parse(string data);
}

假设我有一个这样的设置,每个派生类的预期行为是:

  1. 在基类中调用 Parse 以解析出所有这些输入字符串应该共有的内容。
  2. 派生
  3. 分析应获取特定于派生类的数据。

在这种情况下,在构造函数中使用虚函数是否有意义? 还是我被迫公开"解析"并在每次构造此类时调用它? 还是有其他建议。

我希望这是有道理的,请原谅上面的任何语法错误,我只是想表达一个大致的想法。

还是我被迫公开"解析"并在每次构造此类时调用它?

实际上,在这种情况下,由于您想避免多态行为,我不明白为什么您必须将Parse设置为虚拟方法,甚至是类的非静态方法,因为它不会修改类本身的任何数据成员......例如,您可以将Parse作为私有static方法,然后只需在每个对象的构造函数中调用ClassType::Parse(),您将获得相同的功能。

在构造函数中使用虚函数绝对没有错,只要它适合你。重要的是要记住,从构造函数调用虚函数的多态行为始终仅限于整个层次结构中已经构造的子集。(类似的规则适用于析构函数)。

如果这种受限制的虚拟行为适合您的目的,他们会通过各种方式使用它。

你一定指的是"no-no"参数是一个众所周知的假参数,它基于用户期望调用[尚未构造的]派生类函数的人为前提。为什么有些人把这个发明的错误前提转化为不应该从构造函数调用虚函数的结论,这超出了我的理解。我还没有看到可信的解释。

解决方案非常简单:

class Base
{
public:
    Base(string data)
    {
        Parse(data);
    }
    ~Base(){}
private:
    void Parse(string data);
}
class Derived : public Base
{
public:
    Derived(string data)
    {
        ParseMore(data);
    }
    ~Derived();
private:
    void ParseMore(string data);
}

构造Derived时,在输入 Derived 的构造函数之前调用 Base 的构造函数。因此,在 Base 中进行的分析将完成,您可以在派生构造函数中完成分析。

不要从构造函数调用虚函数。您不会获得多态行为,因为将使用基类虚拟表。

如果你不需要多态行为 - 不要使函数成为虚拟

最简单的解决方案是使用策略模式:定义一个抽象基类Parser,具有纯虚函数parse,以及让派生类传递指向其解析器实例的指针到基类构造函数;即:

class Base
{
protected:
    class Parser
    {
    public:
        virtual ~Parser() {}    // Probably not necessary, since no
                                // one is going to dynamically
                                // allocate any of these, but better
                                // safe than sorry.
        virtual void parse( std::string const& data ) const = 0;
    };
    Base( Parser const& derivedClassParser, std::string const& data )
    {
        derivedClassParser.parse( data );
    }
public:
    //  ...
};
每个派生

类都将定义其解析器,派生自 Base::Parser,定义它的静态实例,并传递此静态实例向下到基类。

还有另一种可能性;如果您有对象的临时实例,但如果某些原因您无法使用上述模式。 基本上,您定义一个在其析构函数中调用虚函数的特殊类,以及具有从 std::string 的隐式转换(也可能是从 char const* 进行隐式转换,以支持传递字符串文本),并声明您的构造函数来获取此类的实例;例如:

class Base
{
public:
    class CallVirtual
    {
        std::string myData;
        mutable Base* myOwner;
        friend class Base;
    public:
        CallVirtual( std::string const& data )
            : myData( data )
            , myOwner( NULL )
        {
        }
        ~CallVirtual()
        {
            if ( myOwner != NULL ) {
                myOwner->Parse( myData );
            }
        }
    };
    Base( CallVirtual const& dataArg )
    {
        dataArg.myOwner = this;
        //  ...
    }
    virtual void Parse( std::string const& data ) ...
};

派生类还应将CallVirtual const&作为参数。然后,在创建派生类的实例时:

Base* p = new Derived( someString );

,字符串自动转换为临时CallVirtual,将在完整表达式的末尾调用其析构函数。