2016-07-23 11 views
1

В настоящее время я делаю относительно небольшой проект в Qt. Есть 2 object s и 2 vector s, которые должны быть доступны на протяжении всей жизни программы. Поэтому для этого я сделал 4 объявления в соответствующих заголовочных файлах, пометил их extern и определил их в MainWindow.cpp, где я использую их в первый раз.
Однако ошибка при запуске std::out_of_range возникает, когда создается один из объектов. После долгой сессии отладки я наконец-то нашел причину и источник ошибки:Объекты не созданы, когда они должны быть

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_YEAR и YEAR_COUNT, то остальная часть заголовка должна быть также, верно?
Вот где я объявить EXTERN объект:

date.h

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

    Year& operator[](int); 

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

ответ

0

date.cpp включает в себя constants.h, который объявляет MONTH_DAY_MAP и MONTH_DAY_MAP_LY глобальные объекты; поэтому эти глобальные объекты определены в блоке перевода date.cpp.

mainwindow.cpp объявляет о своих четырех глобальных объектах. Он создает объект Date. Конструктор Date вызывает конструктор Month, который ссылается на объекты с глобальным охватом от блока перевода date.cpp.

Проблема в том, что C++ не указывает относительный порядок инициализации объектов с глобальной областью в разных единицах перевода. Глобальные объекты из разных единиц перевода могут быть инициализированы во время выполнения в любом порядке.

В этом случае при создании объектов глобальной области mainwindow.cpp объекты глобальной облачностиеще не построены, и доступ к ним приводит к неопределенному поведению и сбою.

Существуют различные решения и способы борьбы с этим static initialization order fiasco. Вы должны найти много материала для изучения и изучения в Google.

+0

Думаю, я получил его, хотя я не мог обмотать голову вокруг этого на некоторое время. Спасибо за Ваш ответ! – 7Y3RPXK3ETDCNRDD

+0

Подождите, стандарт имеет значение: Определяется реализацией независимо от того, выполняется ли динамическая инициализация (8.5, 9.4, 12.1, 12.6.1) объекта пространства имён перед первым утверждением main. Если инициализация отложена до некоторого момента времени после первого утверждения main, она должна произойти до первого использования любой функции или объекта, определенных в той же самой единицы перевода, что и объект, который будет инициализирован. Таким образом, объекты из '' constants.h "' должны быть инициализированы перед вызовом конструктора 'Data'. – 7Y3RPXK3ETDCNRDD

+0

«Первый оператор main()» - это не то же самое, что «единица перевода, которая включает main()». Кроме того, это говорит только о том, что «динамическая инициализация» может возникнуть или не произойти до «main()», но не указывает относительный порядок инициализации динамической инициализации объектов, объявленных в разных единицах перевода, она указывает только определенные вещи о «том же единица перевода как объект, подлежащий инициализации ». –

0

У вас есть несколько проблем.

Вы нарушаете one definition rule. constants.h определяет переменные, которые определяются в каждой единицы перевода, которая включает этот файл. Эти константы должны быть либо определены в анонимном пространстве имен, либо объявили в заголовке, но определил в файле .cpp. Целочисленные константы могут быть объявлены static: их можно затем использовать в контексте постоянного выражения, даже если определение отсутствует.

Глобальные переменные будут страдать от неопределенного порядка инициализации между несколькими единицами перевода. Если у любого из них есть взаимозависимости, даже неявные, вы в конечном итоге будете использовать неинициализированные данные. Так что не делай этого. Вместо этого используйте dependency injection и рассмотрите современную легковесную схему инъекции зависимостей, например, boost.DI.

У вас есть несоответствие между местоположением определения и объявлением глобальных переменных. Это не приводит к неопределенному поведению, но жизнь разработчика сложнее без причины. Если task_vec объявлено в task.h, то оно должно быть определено в task.cpp. В противном случае вы, вероятно, тщательно перепутаете себя и любых сопровождающих, работающих над вашим проектом.

 Смежные вопросы

  • Нет связанных вопросов^_^