Reputation: 477
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
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