1

因此,我从各种在线资源中了解到,从构造函数中调用虚函数通常是禁忌。我意识到这里的问题是首先构造基类,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. 派生解析应该获取特定于派生类的数据。

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

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

4

5 回答 5

1

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

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

于 2012-06-27T17:28:08.257 回答
1

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

如果这种受限制的虚拟行为适合您的目的,他们会尽一切努力使用它。

您必须引用的“no-no”参数是一个众所周知的假参数,基于用户期望调用 [not-yet-constructed] 派生类的函数的人为前提。为什么有些人将发明的错误前提转化为不应从构造函数调用虚函数的结论,这超出了我的理解。我还没有看到可靠的解释。

于 2012-06-27T18:38:53.933 回答
0

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

如果您不需要多态行为 - 不要将函数设为虚拟

于 2012-06-27T17:27:16.587 回答
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被构造时,在你进入 的构造函数之前Base调用的构造函数。因此在 Base 中进行的解析将完成,您可以在 Derived 构造函数中完成解析。Derived

于 2012-06-27T18:27:51.243 回答
0

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

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的,其析构函数将在完整表达式的末尾调用。

于 2012-06-27T18:41:27.610 回答