1

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;
Chechen Itza
  • 111
  • 8

2 Answers2

0

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.

Community
  • 1
  • 1
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • I think i got it, though i couldn't wrap my head around this for a while. Thanks for your answer! – Chechen Itza Jul 24 '16 at 13:48
  • Wait , the standard has this : It is implementation-defined whether or not the dynamic initialization (8.5, 9.4, 12.1, 12.6.1) of an object of namespace scope is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first use of any function or object defined in the same translation unit as the object to be initialized. So the objects from `"constants.h"` have to be initialized before `Data` constructor call. – Chechen Itza Jul 24 '16 at 14:58
  • "first statement of main()" is not the same thing as "the translation unit that includes main()". Furthermore, this only states that "dynamic initialization" may or may not occur before `main()`, but does not specify the relative initialization order of dynamic initialization of objects declared in different translation units, it only specifies certain things about the "same translation unit as the object to be initialized". – Sam Varshavchik Jul 24 '16 at 23:35
0

You have several problems.

You violate the one definition rule. constants.h defines variables that get defined in each translation unit that includes that file. These constants should either be defined in an anonymous namespace, or declared in the header but defined in a .cpp file. The integer constants could be declared static: they can then be used in a context of constant expression even if the definition is missing.

The global variables will suffer from the undefined initialization order between multiple translation units. If any of them have interdependencies, even implicit ones, you'll end up using uninitialized data somewhere. So don't do that. Instead, use dependency injection, and consider a modern lightweight dependency injection framework like e.g. boost.DI.

You have a mismatch between the location of the definition and declaration of global variables. This doesn't lead to undefined behavior, but the developer's lives harder for no reason. If task_vec is declared in task.h, then it should be defined in task.cpp. Otherwise you're likely to thoroughly confuse yourself and any maintainers working on your project.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313