在头文件中的静态成员函数内声明了多少个静态数据实例?

时间:2011-02-01 18:08:20

标签: c++ static

我正在使用xlC在AIX上编译Cppcheck。每个检查器类都派生自Check类,其构造函数负责在全局静态列表中注册该类检查器。

以下是相关代码的相关部分(文件名链接到Github上的完整源代码):

check.h

class Check {
public:
    Check() {
        instances().push_back(this);
        instances().sort();
    }
    static std::list<Check *> &instances() {
        static std::list<Check *> _instances;
        return _instances;
    }
    // ...
};

checkbufferoverrun.h

class CheckBufferOverrun: public Check {
    // ...
};

checkbufferoverrun.cpp

// Register this check class (by creating a static instance of it)
namespace
{
CheckBufferOverrun instance;
}

注意如何在 header 文件中的_instances函数内声明static静态变量(没有相应的check.cpp文件)。使用g++进行编译时,编译器和链接器协同工作以确保静态instances()函数只有一个实现,因此只有静态_instances列表的一个实例。在不同.cpp文件中实例化的所有不同检查器类都会在同一个_instances列表中注册。

但是,在AIX的xlC下,相同的代码最终会为包含它的每个instances()文件创建一个不同的 .cpp函数。具有不同的静态_instances列表。因此,不再存在单个中心_instances列表,这会导致Cppcheck无法运行大部分检查。

在这种情况下哪个编译器的行为是正确的?

更新:这个问题不是关于如何修复问题,我已经这样做了。我很好奇哪种行为正确

3 个答案:

答案 0 :(得分:9)

g ++具有正确的行为:应该只有一个对象实例。 C ++标准说(C ++ 03 7.1.2 / 4):

  

extern内联函数中的静态局部变量始终引用同一个对象。

因为类有外部链接,静态成员函数也有外部链接,按照C ++ 03 3.5 / 5:

  

成员函数...如果类的名称具有外部链接,则具有外部链接。

因为成员函数是在类定义中定义的,所以它是一个内联函数,根据C ++ 03 7.1.2 / 3:

  

在类定义中定义的函数是内联函数。

答案 1 :(得分:2)

在这种特殊情况下,g ++是正确的(假设instances()静态成员函数返回一个引用,这是一个错字,对吧?)。您可能希望将instances()函数的定义移动到cpp文件,这应该是在xlc中的解决方法。

答案 2 :(得分:1)

纠正于:

   static std::list<Check *>& instances();

// check.cpp
 std::list<Check *> & Check::instances()
 {
     static std::list<Check *> _instances;
     return _instances;
 }

如果在头文件中内联函数,则每个编译单元最初将编译自己的定义。但是,在链接时,由于 One-Definition Rule,它们应该缩减为一个实例。

我确信您知道,Check的构造函数不是线程安全的。你的单例构造函数也不是完全线程安全的,因为虽然ODR确保它们之间的线程只能创建一个实例,但list的构造函数不是原子的,因此我不确定它是否保证在另一个线程看到之前完全构造它

对于构建单例或非原子静态的完全线程安全的方法,可以使用boost :: once。