3

我正在使用编译器在 AIX上构建CppcheckxlC(请参阅上一个问题)。Checker 类都派生自一个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());
    }
};

检查缓冲区溢出.h

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

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

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

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

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

4

4 回答 4

4

是的,它是未定义的。该标准在 10.4/6 中特别说明了这一点

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

于 2011-02-01T21:50:18.300 回答
2

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

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

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

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

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

那么也许这将有助于了解发生了什么。

于 2011-02-01T21:49:51.690 回答
0

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

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

class check {
private Check () { ... }
public:
    static Check* createInstance() {
        Check* check = new Check();
        instances().push_back(check);
        instances().sort();
    }
...
}
于 2011-02-01T21:56:27.223 回答
0

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

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

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

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

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

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

因此 Checker 子类可能如下所示:

class CheckBufferOverrun: public Check { protected: CheckBufferOverrun : Check("Bounds checks") { // 因为每个派生都有一个名称,为什么不直接将其作为 arg 传递呢?} public: CheckBufferOverrun makeCheckBufferOverrun() { CheckBufferOverrun that = new CheckBufferOverrun();

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

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

于 2011-02-01T22:10:02.557 回答