Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing pointers to static variables

I've created a unit test framework for c++ which I want to port to C later and I've come across a problem where a unit test simply won't run. Unit tests are created in .cpp files and only one .cpp file should run all the tests.

To simplify a bit, this is how a test is typically created:

main.cpp

#define UNIT_TEST_IMPL // or whatever
#include "unit_test.hpp"

int main()
{
    for(const auto& test : tests_queue)
        test->run();
    return 0;
}

unit_test.hpp

#pragma once

struct Base
{
protected:
    Base() = delete;
    Base(Base* ptr);

public:
    virtual void run() = 0;
};

#if defined(UNIT_TEST_IMPL)

#include <vector>

std::vector<Base*> tests_queue;

Base::Base(Base* ptr)
{
    tests_queue.push_back(ptr);
}

#endif

test.cpp

#include "unit_test.hpp"

#include <iostream>

struct Test : Base
{
    Test()
        : Base(this)
    {}

    void run() override
    {
        std::cout << "new test" << std::endl;
    }

};

struct Test2 : Base
{
    Test2()
        : Base(this)
    {}

    void run() override
    {
        std::cout << "new test2" << std::endl;
    }
};

static Test test;
static Test2 test2;

The question is: why is it not running the tests defined in test.cpp (if I create tests in the main.cpp file they run perfectly fine)? My guess is that the problem lies in the way I am storing Base pointers but I don't know. The compiler is g++ 6.4.0

like image 623
Alexandru Ica Avatar asked Feb 18 '26 12:02

Alexandru Ica


1 Answers

static-initialization-order-fiasco in action:

Initialization order of global across translation units is unspecified, so if test, test2 are instantiated before tests_queue, the later initialization would destroy the registration.

One possible correction:

#pragma once

struct Base
{
protected:
    Base();

public:
    virtual ~Base() = default;
    virtual void run() = 0;
};

#if defined(UNIT_TEST_IMPL) // Should be defined only once.

#include <vector>

std::vector<Base*>& get_tests_queue()
{
    static std::vector<Base*> tests_queue;
    return tests_queue;
}

Base::Base()
{
    get_tests_queue().push_back(this);
}

#endif

so your main.cpp would be:

#define UNIT_TEST_IMPL // or whatever
#include "unit_test.hpp"

int main()
{
    for(const auto& test : get_tests_queue())
        test->run();
}

Your unitTests would be unmodified:

#include "unit_test.hpp"

#include <iostream>

struct Test : Base
{
    void run() override { std::cout << "new test" << std::endl; }
};

struct Test2 : Base
{
    void run() override { std::cout << "new test2" << std::endl; }
};

static Test test;
static Test2 test2;

Demo

like image 180
Jarod42 Avatar answered Feb 20 '26 00:02

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!