Search code examples
c++qtexternlinkage

Objects are not created when they should be


I'm currently making a relatively small project in Qt. There are 2 objects and 2 vectors that have to be available throughout the whole program life. So to achieve that, I made 4 declarations in corresponding header files, marked them extern and defined them in MainWindow.cpp where i use them for the first time.
However, runtime error std::out_of_range occurs when one of the object is being created. After long session of debugging i finally found the cause and source of error :

MainWindow.cpp

#include "task.h"  //Vectors; Works
#include "date.h" //Error
#include "db.h"  //Works

std::vector<Task> task_vec; //extern from task.h
std::vector<Group> group_vec; //extern from task.h
Date date; //extern from date.h <- Error when instantinating this one
Database db; //extern from db.h

MainWindow::MainWindow(){//...}
//date and db objects are used in this file

date.cpp

#include "date.h" //it has "consants.h" included in it

//..Stuff
Date::Date()
{
    //Use const int variable from "constants.h"
    year = constants::START_YEAR; //Works, START_YEAR is initialized
    year_count = constants::YEAR_COUNT //Works aswell
    Month month(m, y);
}
Month::Month(int month, int year)
{
    //Use const std::map<QString, std::pair<int,int>> from "constants.h"
    day_count = constants::MONTH_DAY_MAP_LY.at(0).second //ERROR, MONTH_DAY_MAP_LY is not initialized
}

constants.h

namespace constants {
const int START_YEAR = 2016;
const int YEAR_COUNT = 83;

const QList<QString> MONTH { "January", "February", "March",
        "April", "May", "June", "July", "August", "September", "October", "November", "December"};

const std::map<QString, std::pair<int, int>> MONTH_DAY_MAP{
    {MONTH[0], std::make_pair(0, 31)}, {MONTH[1], std::make_pair(1, 28)}, {MONTH[2], std::make_pair(2, 31)},
    {MONTH[3], std::make_pair(3, 30)}, {MONTH[4], std::make_pair(4, 31)}, {MONTH[5], std::make_pair(5, 30)},
    {MONTH[6], std::make_pair(6, 31)}, {MONTH[7], std::make_pair(7, 31)}, {MONTH[8], std::make_pair(8, 30)},
    {MONTH[9], std::make_pair(9, 31)}, {MONTH[10], std::make_pair(10, 30)}, {MONTH[11], std::make_pair(11, 31)}
};
const std::map<QString, std::pair<int, int>> MONTH_DAY_MAP_LY {
    {MONTH[0], std::make_pair(0, 31)}, {MONTH[1], std::make_pair(1, 29)}, {MONTH[2], std::make_pair(2, 31)},
    {MONTH[3], std::make_pair(3, 30)}, {MONTH[4], std::make_pair(4, 31)}, {MONTH[5], std::make_pair(5, 30)},
    {MONTH[6], std::make_pair(6, 31)}, {MONTH[7], std::make_pair(7, 31)}, {MONTH[8], std::make_pair(8, 30)},
    {MONTH[9], std::make_pair(9, 31)}, {MONTH[10], std::make_pair(10, 30)}, {MONTH[11], std::make_pair(11, 31)}
};
}

I have no idea why. If the START_YEAR and YEAR_COUNT are initialized, then the rest of the header should be aswell, right?
Here's where i declare extern object:

date.h

//...Stuff
class Date
{
public:
    Date();

    Year& operator[](int);

private:
    std::array<Year, constants::YEAR_COUNT> date_arr;
} extern date;

Solution

  • date.cpp includes constants.h, which declares MONTH_DAY_MAP and MONTH_DAY_MAP_LY global objects; hence those global objects are defined in the date.cpp translation unit.

    mainwindow.cpp declares its four global objects. It constructs a Date object. Date's constructor invokes Month's constructor, which references the globally-scoped objects from the date.cpp translation unit.

    The problem is that C++ does not specify the relative order of initialization of globally scoped objects in different translation units. Global objects from different translation units can be initialized, at runtime, in any order.

    In this case, at the time that mainwindow.cpp's globally scoped objects get constructed, mainwindow.cpp's globally scoped objects are not yet constructed, and accessing them results in undefined behavior, and a crash.

    There are various solutions, and ways to deal with this static initialization order fiasco. You should find plenty of material to study and learn about, in Google.