Objects 没有在应该创建的时候创建

Objects are not created when they should be

我目前正在用 Qt 做一个相对较小的项目。有 2 objects 和 2 vectors 必须在整个程序生命周期中可用。所以为了实现这一点,我在相应的 header 文件中做了 4 个声明,将它们标记为外部并在我第一次使用它们的 MainWindow.cpp 中定义它们。
但是,在创建 object 之一时发生运行时错误 std::out_of_range。经过长时间 session 的调试,我终于找到了错误的原因和来源:

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

我不知道为什么。如果 START_YEARYEAR_COUNT 被初始化,那么 header 的其余部分也应该被初始化,对吧?
这是我声明 extern object 的地方:

date.h

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

    Year& operator[](int);

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

date.cpp 包括 constants.h,它声明了 MONTH_DAY_MAPMONTH_DAY_MAP_LY 全局对象;因此,这些全局对象在 date.cpp 翻译单元中定义。

mainwindow.cpp 声明了它的四个全局对象。它构造了一个 Date 对象。 Date 的构造函数调用 Month 的构造函数,它引用来自 date.cpp 翻译单元的全局范围的对象。

问题是 C++ 没有指定不同翻译单元中全局作用域对象的相对初始化顺序。来自不同翻译单元的全局对象可以在运行时以任何顺序初始化。

在这种情况下,在构建 mainwindow.cpp 的全局范围对象时,尚未构建 mainwindow.cpp 的全局范围对象,访问它们会导致未定义的行为,并且崩溃。

有多种解决方案和方法可以解决此问题 static initialization order fiasco。您应该在 Google.

中找到大量 material 来研究和学习

你有几个问题。

你违反了one definition ruleconstants.h 定义在包含该文件的每个翻译单元中定义的变量。这些常量应该在匿名命名空间中定义,或者在 header 中 声明 但在 .cpp 文件中 定义 .整数常量可以声明为静态的:即使定义缺失,它们也可以在常量表达式的上下文中使用。

全局变量将受到多个翻译单元之间未定义的初始化顺序的影响。如果它们中的任何一个具有相互依赖性,甚至是隐含的依赖性,您最终都会在某处使用未初始化的数据。所以不要那样做。相反,使用 dependency injection, and consider a modern lightweight dependency injection framework like e.g. boost.DI.

全局变量的定义位置与声明位置不匹配。这不会导致未定义的行为,但开发人员的生活无缘无故地更加艰难。如果在task.h中声明了task_vec,那么它应该在task.cpp中定义。否则,您可能会彻底混淆自己和任何从事您项目的维护人员。