【问题标题】:Is calling pure virtual functions indirectly from a constructor always undefined behaviour?从构造函数间接调用纯虚函数总是未定义的行为吗?
【发布时间】:2011-06-19 14:06:00
【问题描述】:

我正在使用xlC 编译器在AIX 上构建Cppcheck(请参阅previous question)。 Checker 类都派生自 Check 类,其构造函数将每个对象注册到全局列表中:

check.h

class Check {
public:
    Check() {
        instances().push_back(this);
        instances().sort();
    }
    static std::list<Check *> &instances();
    virtual std::string name() const = 0;
private:
    bool operator<(const Check *other) const {
        return (name() < other->name());
    }
};

checkbufferoverrun.h

class CheckBufferOverrun: public Check {
public:
    // ...
    std::string name() const {
        return "Bounds checking";
    }
};

我似乎遇到的问题与instances().sort() 通话有关。 sort() 将调用 Check::operator&lt;(),后者在静态 instances() 列表中的每个指针上调用 Check::name(),但刚刚添加到列表中的 Check 实例尚未完全运行其构造函数(因为它仍在内部Check::Check())。因此,在 CheckBufferOverrun 构造函数完成之前对此类指针调用 -&gt;name() 应该是未定义的行为。

这真的是未定义的行为,还是我在这里遗漏了一个微妙之处?

请注意,我不认为对sort() 的调用是严格要求的,但效果是Cppcheck 以确定的顺序运行其所有检查器。这只会影响检测到错误的顺序的输出,这会导致某些测试用例失败,因为它们期望以特定顺序输出。

更新:上面的问题(大部分)仍然存在。但是,我认为在构造函数中调用sort() 没有引起问题(即通过调用纯虚函数而崩溃)的真正原因是Check::operator&lt;(const Check *) 从未真正被sort() 调用!相反,sort() 似乎改为比较 指针。在g++xlC 中都会出现这种情况,表明 Cppcheck 代码本身存在问题。

【问题讨论】:

  • Check 构造函数添加一个字符串“name”参数,并让后代在构造时提供一个值。将name() 设为非虚拟并返回构造函数传入的值。此处显示的 Check 构造函数的其余部分可以保持原样。
  • @Rob Kennedy:这是一个很好的解决方案,我会向维护人员提出这个建议。
  • 是的,并考虑是否值得为实例使用集合而不是列表 - 这可能比在每次插入后显式调用排序更快。顺便说一句,您的 operator

标签: c++ constructor pure-virtual


【解决方案1】:

是的,它是未定义的。该标准在 10.4/6 中明确说明

可以从抽象类的构造函数(或析构函数)调用成员函数;对于从这样的构造函数(或析构函数)创建(或销毁)的对象,直接或间接地对纯虚函数进行虚调用(10.3)的效​​果是未定义的。

【讨论】:

    【解决方案2】:

    确实,从构造函数调用纯虚函数始终是未定义的行为。

    在构造函数完全运行(关闭“}”)之前,不能假设虚拟指针被设置,因此任何对虚拟函数(或纯虚拟函数)的调用都必须在编译时设置(静态绑定调用)。

    现在,如果虚函数是纯虚函数,编译器一般会为这种纯虚函数插入自己的实现,默认行为是产生分段错误。标准并没有规定纯虚函数应该如何实现,但大多数 C++ 编译器都采用上述风格。

    如果您的代码没有引起任何运行时恶作剧行为,那么它就不会在所述调用序列中被调用。如果您可以发布以下2个功能的实现代码

    instances().push_back(this);
    instances().sort();
    

    那么也许看看发生了什么会有所帮助。

    【讨论】:

    • 问题中的文件名链接到 Github 上的实际完整代码。
    • 对不起,我不知道这是一个链接。我正在查看 Check.h 文件,但我没有看到该标头中声明的任何 sort() 函数。 :(
    • 对不起。我的错。它是列表的函数。
    • 没有编译器的行为是“产生分段错误”。这是什么废话?
    • 这是我在 symbian 时代使用 ms vc++ ide 时所看到的。另外,对于更合乎逻辑的解释,请参阅:stackoverflow.com/questions/99552/…该问题与此答案不完全相关,但调用纯虚函数会导致段错误。
    【解决方案3】:

    只要对象构造没有完成,就不能调用纯虚函数。但是,如果它在基类 A 中声明为纯虚拟,然后在 B 中定义(从 A 派生),则 C 的构造函数(从 B 派生)可能会调用它,因为 B 的构造已经完成。

    在您的情况下,请改用静态构造函数:

    class check {
    private Check () { ... }
    public:
        static Check* createInstance() {
            Check* check = new Check();
            instances().push_back(check);
            instances().sort();
        }
    ...
    }
    

    【讨论】:

    • 你不能创建一个 ABC 的实例,所以这个语句甚至不应该编译: Check* check = new Check();
    • 您能详细说明一下吗?我不确定我理解你的意思(或者你不理解这个例子)。除此之外,我的代码中缺少一个冒号。
    • 哦,我明白了...您指的是 Check 类,而不是我给出的示例中的 ABC。是的,Check 不能被实例化——我忽略了 Check 本身是声明纯虚函数的基类。
    【解决方案4】:

    我认为您真正的问题是您将两件事混为一谈:Checker 基类和一些用于注册(派生)Check 实例的机制。

    除其他外,这不是特别健壮:我可能想使用您的 Checker 类,但我可能想以不同的方式注册它们。

    也许你可以做这样的事情:Checker 得到一个受保护的 ctor(反正它是抽象的,所以只有派生类应该调用 Checker ctor)。

    派生类还具有受保护的 ctor,以及用于创建实例的公共静态方法(“命名构造函数模式”)。该创建方法通知 Checker 子类,然后他们将其(此时完全创建)传递给 CheckerRegister 类(这也是抽象的,因此用户可以在需要时实现自己的)。

    您可以使用您喜欢的任何单例模式或依赖注入机制来实例化 Checkerregister 并使其可用于 Checker 子类。

    一种简单的方法是在 Checker 上使用 getCheckerRegister 静态方法。

    所以 Checker 子类可能如下所示:

    类 CheckBufferOverrun:公共检查 { 受保护: CheckBufferOverrun : Check("边界检查") { // 因为每个派生都有一个名字,为什么不把它作为一个 arg 传递呢? } 民众: CheckBufferOverrun makeCheckBufferOverrun() { CheckBufferOverrun that = new CheckBufferOverrun();

       // get the singleton, pass it something fully constructed
       Checker.getCheckerRegister.register(that) ;
       return that;
    }
    

    如果看起来这最终会成为大量样板代码,请编写一个模板。如果您担心因为 C++ 中的每个模板实例都是真实且唯一的类,请编写一个非模板基类来注册任何 Checker 派生的。

    【讨论】:

    • 是的,Cppcheck 中使用的注册机制似乎是为了尽量减少实现Check 的新子类并让主检查循环使用它所需的“连接器”代码量。理论上,只需要声明并实现一个来自Check 的派生类,创建一个全局静态实例,程序的其余部分就会开始使用它。我可能不会选择这样做。
    猜你喜欢
    • 2012-01-28
    • 2012-01-27
    • 2021-10-20
    • 2013-07-03
    • 2010-10-05
    • 2012-01-28
    相关资源
    最近更新 更多