Как исправить порядок инициализации статического члена класса?

Выдает ли следующий код SIGSEGV или работает так, как ожидалось, зависит от порядка, в котором объектные файлы появляются в make-файле (в моем случае в .pro). Я не очень уверен, что мне просто нужно поддерживать их в правильном порядке. Есть ли простое изменение в моих источниках, которое устраняет зависимость от правильного порядка объектных файлов?

Это исходные файлы, сведенные к короткому примеру.

dummy0.h:

#ifndef DUMMY0_H
#define DUMMY0_H

#include <QObject>

class Dummy0 : public QObject
{
    Q_OBJECT
public:
    explicit Dummy0(QObject *parent = 0);
};

#endif // DUMMY0_H

dummy0.cpp:

#include "dummy0.h"
#include "unittestclass.h"

Dummy0::Dummy0(QObject *parent) : QObject(parent) {}

ADD_TEST( Dummy0 )

Как следует из названия, в будущем также будут Dummy1...Dummy<n>.

unittestclass.h:

#ifndef UNITTESTCLASS_H
#define UNITTESTCLASS_H

#include <QObject>
#include <QTest>
#include <QDebug>
#include <memory>
#include <list>

// see https://stackoverflow.com/questions/12194256/qt-how-to-organize-unit-test-with-more-than-one-class

namespace TestCollector {

  class BaseClass {  // see https://hackernoon.com/shared-static-variable-for-all-template-class-instances-eaed385f332b
  public:
      BaseClass() = default;
      int listClasses( int argc, char *argv[] );
  protected:
      static std::list<std::shared_ptr<QObject>> testlist;
  };

  template <class T>
  class UnitTestClass : public BaseClass {

  public:
      UnitTestClass() {
          std::shared_ptr<QObject> ptr = std::make_shared<T>();
          qDebug() << "pushing a" << ptr.get()->metaObject()->className();
          qDebug() << "testlist size before:" << testlist.size() << "of" << testlist.max_size();
          testlist.size();  // no SIGSEGV !?
          testlist.push_back( ptr );   // SIGSEGV here when race condition
          qDebug() << "testlist size after:" << testlist.size();
      }
  };
}  // TestCollector

#define ADD_TEST(className) static TestCollector::UnitTestClass<className> test;

#endif // UNITTESTCLASS_H

unittestclass.cpp:

#include "unittestclass.h"

namespace TestCollector {

std::list<std::shared_ptr<QObject>> BaseClass::testlist;

int BaseClass::listClasses( int argc, char *argv[] ) {
    int result=0;

    for( const auto c : testlist ) {
        qDebug() << "got" << c.get()->metaObject()->className();
    }
    return result;
}

}  // TestCollector

main.cpp:

#include "unittestclass.h"

int main(int argc, char *argv[]) {
    TestCollector::BaseClass base;
    return base.listClasses( argc, argv );
}

Когда у меня есть dummy0.cpp перед unittestclass.cpp, программа завершается сбоем на testlist.push_back(ptr) [примечание: достаточно интересно, что testlist.size() в строке перед push_back не даст сбоя]. Когда позже появляется dummy0.cpp, все в порядке. Это указывает на проблему, заключающуюся в том, что тестовый список может использоваться до его инициализации, когда порядок объектных файлов неблагоприятен!?

Как я могу избежать зависимости от этого порядка?

[В настоящее время я использую c++11, QtCreator 3.5.1, g++ 5.4.0, GNU ld 2.26.1.]


person kaba    schedule 20.02.2019    source источник
comment
То, что вы испытываете здесь, не является состоянием гонки. Это может быть интересным. Как и это   -  person Jabberwocky    schedule 20.02.2019
comment
Фиаско статического порядка инициализации.   -  person drescherjm    schedule 20.02.2019
comment
Это не состояние гонки, все из-за того, что С++ 11 гарантирует, что статические члены класса инициализируются потокобезопасным способом. Проблема указана в комментарии @drescherjm.   -  person PaulMcKenzie    schedule 20.02.2019


Ответы (1)


Вы можете убедиться, что объект, от которого вы зависите, существует, косвенно через функцию:

class BaseClass
{
// ...
    // This returns a pointer only to make it hard to copy the list by mistake.
    // Some people prefer to use a reference, and some prefer dynamic allocation.
    // The important part is that the object is created the first time you use it.
    static std::list<std::shared_ptr<QObject>>* get_testlist()
    {
        static std::list<std::shared_ptr<QObject>> tests;
        return &tests;
    } 
};

а затем используйте его примерно так:

UnitTestClass() {
      std::shared_ptr<QObject> ptr = std::make_shared<T>();
      qDebug() << "pushing a" << ptr.get()->metaObject()->className();
      auto testlist = get_testlist();
      qDebug() << "testlist size before:" << testlist->size() << "of" << testlist->max_size();
      testlist->push_back( ptr );
      qDebug() << "testlist size after:" << testlist->size();
  } 
person molbdnilo    schedule 20.02.2019