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

时间:2012-06-27 17:24:30

标签: c++

所以我从各种在线资源中了解到,在构造函数中调用虚函数通常是禁止的。我意识到这里的问题是首先构造基类,C ++首先调用Base类的函数版本。但是我有一个独特的用例可能对此没问题。我很感激一些评论。考虑一下这种情况。

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. 在这种情况下,在构造函数中使用虚函数是否有意义?或者我是否被迫公开“解析”并在每次构建这个课程时调用它?还是有其他建议。

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

5 个答案:

答案 0 :(得分:1)

  

或者我是否被迫公开“解析”并在每次构建这个类时调用它?

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

答案 1 :(得分:1)

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

如果此受限制的虚拟行为适合您的目的,他们一定会使用它。

你必须引用的“禁忌”参数是一个众所周知的伪论证,它基于用户期望调用[尚未构造的]派生类的功能的人为前提。为什么有些人将发明错误前提的人转化为不应该从构造函数中调用虚函数的结论超出我的意思。我还没有看到可信的解释。

答案 2 :(得分:0)

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

如果您不需要多态行为 - 请不要使函数虚拟

答案 3 :(得分:0)

解决方案非常简单:

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时,{<1}}的构造函数在之前被称为,您输入Base的构造函数。 因此,在Base中进行的解析将完成,您可以在Derived构造函数中完成解析。

答案 4 :(得分:0)

最简单的解决方案是使用策略模式:定义一个 抽象基类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*进行隐式转换,以支持传递字符串文字),并声明你的 构造函数来获取此类的实例; e.g:

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

派生类也应该以{{1​​}}为参数。 然后,当您创建派生类的实例时:

CallVirtual const&

,字符串自动转换为临时Base* p = new Derived( someString ); , 其析构函数将在完整表达式结束时调用。