kaba
kaba

Reputation: 477

How to fix initialisation order of static class member?

Whether the following code throws a SIGSEGV or works as expected depends on the order in which object files appear in the makefile (in the .pro in my case). I'm not very confident with just having to maintain them in the correct order. Is there a simple change to my sources, which eliminates the dependency on the correct order of object files?

These are the source files, boiled down to a short example.

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 )

As the name suggests, in the future there will also be 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 );
}

When I have dummy0.cpp before unittestclass.cpp the programme fails at testlist.push_back(ptr) [NB: interestingly enough, the testlist.size() in the line before push_back won't fail]. When dummy0.cpp appears later, everthing is fine. This hints to a problem that testlist might be used before it is initialised, when the order of object files is unfavourable!?

How can I avoid depending on this order?

[I currently use c++11, QtCreator 3.5.1, g++ 5.4.0, GNU ld 2.26.1.]

Upvotes: 1

Views: 104

Answers (1)

molbdnilo
molbdnilo

Reputation: 66371

You can ensure that the object you're depending on exists by indirecting through a function:

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;
    } 
};

and then use it something like this:

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();
  } 

Upvotes: 1

Related Questions