Глобальный объект и порядок создания

Я все еще изучаю С++. У меня есть одна проблема. Допустим, в вашем проекте есть глобальный объект, который всегда существует, например, ApiManager, и все остальные модули имеют к нему доступ (по #include). На данный момент я делаю это:

Заголовок:

class ApiManager : public QObject
{
    Q_OBJECT
public:
    explicit ApiManager(QObject *parent = 0);
signals:
public slots:
};

extern ApiManager apiMng;

Источник:

ApiManager apiMng;

Проблема в том, что другие объекты также должны иметь доступ при инициализации, и я заметил, что глобальные объекты С++ создаются в алфавитном порядке. Мне интересно, как вы с этим справляетесь? Существует какой-то трюк для этого? Например, в мире Free Pascal каждый модуль класса имеет разделы initialization и finalization:

Type
  TApiManager = class
end;

var ApiMng: TApiManager;

initialization
  ApiMng := TApiManager.Create;
finalization
  ApiMng.Free;

... и initialization порядок модулей проекта может быть отсортирован в исходном коде проекта в предложении uses (например, #include в C++). Я знаю, что есть много способов сделать это (например, инициализировать все в main.cpp с нестандартным порядком), но хочу знать, что такое «хорошая привычка» в мире C++

Редактировать: Решено Q_GLOBAL_STATIC (представлено в Qt 5.1, но работает и для Qt 4.8), но все еще есть две проблемы:

  1. До сих пор не знаю, как управлять заказами конструктора (и где его инициализировать). Поскольку глобальные объекты, созданные Q_GLOBAL_STATIC, не создаются при запуске приложения. Они создаются при первом использовании. Поэтому мне нужно где-то "тронуть" этот объект (в main.cpp?) с помощью моего пользовательского заказа.

  2. В документации говорится, что Q_GLOBAL_STATIC должен вызываться в теле файла .cpp, а не в заголовке. Но тогда другие классы не видят этот объект. Итак, я создал статическую функцию, которая предоставляет ссылку на этот объект:

.cpp:

Q_GLOBAL_STATIC(ApiManager, apiMng)
ApiManager *ApiManager::instance()
{
    return apiMng();
}

Но из этой темы: http://qt-project.org/forums/viewthread/13977 Q_GLOBAL_STATIC должен автоматически отображать экземпляр, но не


person Dibo    schedule 17.12.2013    source источник
comment
Объявление 1). Ну, может быть, я не уловил этого в битве между C++ и Qt. Реальный пример: Использование Q_GLOBAL_STATIC недостаточно, мне нужно сделать в main.cpp: // Инициализировать глобальные объекты ApiManager::instance(); AmarokPlugin::instance(); Объявление 2). Извини. Из этого разговора я подумал, что Q_GLOBAL_STATIC волшебным образом раскрывает метод instance(), но мне нужно определить его самостоятельно   -  person Dibo    schedule 20.12.2013


Ответы (3)


Они не инициализируются в алфавитном порядке, а порядок инициализации среди единиц перевода не определен, поскольку стандарт ничего не гарантирует по этому поводу.

Почему глобальные переменные — это зло

Глобальных переменных следует избегать по нескольким причинам, но основная причина заключается в том, что они значительно усложняют вашу программу. Например, предположим, что вы изучаете программу и хотите знать, для чего используется переменная с именем g_nValue. Поскольку g_nValue является глобальным, а глобальные значения могут использоваться в любом месте всей программы, вам придется проверять каждую строку каждого отдельного файла! В компьютерной программе с сотнями файлов и миллионами строк кода вы можете себе представить, сколько времени это займет!

Во-вторых, глобальные переменные опасны, потому что их значения могут быть изменены любой вызванной функцией, и у программиста нет простого способа узнать, что это произойдет.

Почему следует избегать глобальных переменных, когда они не нужны

Нелокальность. Исходный код легче всего понять, когда объем его отдельных элементов ограничен. Глобальные переменные могут быть прочитаны или изменены любой частью программы, что затрудняет запоминание или обдумывание каждого возможного использования.

Нет контроля доступа или проверки ограничений. Глобальная переменная может быть получена или установлена ​​любой частью программы, и любые правила, касающиеся ее использования, могут быть легко нарушены или забыты. (Другими словами, методы доступа get/set, как правило, предпочтительнее, чем прямой доступ к данным, и тем более для глобальных данных.) Кроме того, отсутствие контроля доступа сильно мешает достижению безопасности в ситуациях, когда вы можете захотеть запустить ненадежный код. (например, работа со сторонними плагинами). Неявная связь. Программа со многими глобальными переменными часто имеет тесную связь между некоторыми из этих переменных, а также связь между переменными и функциями. Группировка связанных элементов в связные единицы обычно приводит к улучшению программ.

Проблемы параллелизма — если к глобальным объектам можно получить доступ из нескольких потоков выполнения, синхронизация необходима (и слишком часто ею пренебрегают). При динамическом связывании модулей с глобальными переменными составная система может оказаться небезопасной для потоков, даже если два независимых модуля, протестированных в десятках различных контекстов, были безопасными.

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

Тестирование и ограничение — исходный код, использующий глобальные переменные, тестировать несколько сложнее, потому что нельзя легко настроить «чистую» среду между запусками. В более общем случае источник, который использует глобальные службы любого рода (например, чтение и запись файлов или баз данных), которые явно не предоставлены этому источнику, трудно тестировать по той же причине. Для взаимодействующих систем возможность тестирования системных инвариантов может потребовать одновременного запуска более одной «копии» системы, что сильно затрудняется любым использованием общих служб, включая глобальную память, которые не предназначены для совместного использования в рамках теста. .

В общем, избегайте глобальных переменных. Если они вам нужны, используйте Q_GLOBAL_STATIC.

Создает глобальный и статический объект типа QGlobalStatic с именем VariableName, который ведет себя как указатель на Type. Объект, созданный Q_GLOBAL_STATIC, инициализирует себя при первом использовании, что означает, что он не увеличит время загрузки приложения или библиотеки. Кроме того, объект инициализируется потокобезопасным способом на всех платформах.

Вы также можете использовать Q_GLOBAL_STATIC_WITH_ARGS. Здесь вы можете найти некоторые встроенные выделения из документации:

Создает глобальный и статический объект типа QGlobalStatic с именем VariableName, инициализированный аргументами Arguments и ведущий себя как указатель на Type. Объект, созданный Q_GLOBAL_STATIC_WITH_ARGS, инициализирует себя при первом использовании, что означает, что он не увеличит время загрузки приложения или библиотеки. Кроме того, объект инициализируется потокобезопасным способом на всех платформах.

Некоторые люди также склонны создавать функции для их обертывания, но они не уменьшают значительно сложность, и в конечном итоге они либо забывают сделать эти функции потокобезопасными, либо добавляют больше сложности. Забудьте об этом, когда вы можете .

person lpapp    schedule 17.12.2013
comment
Похоже на идеальное решение, но Q_GLOBAL_STATIC был представлен в Qt 5.1, а мне нужно для Qt 4.8:/ - person Dibo; 18.12.2013
comment
Ну, вроде работает, но посмотри мой первый пост, я его отредактировал - person Dibo; 20.12.2013
comment
Ну, я не понимаю идею stackoverflow, я хотел объяснить все в этом комментарии, чтобы сохранить историю, но комментарии имеют ограничение на количество символов, и я решил просто отредактировать основной пост. - person Dibo; 20.12.2013

Порядок инициализации глобальных объектов определяется только внутри единицы трансляции (там это сверху вниз). Нет никакой гарантии между единицами перевода. Типичный обходной путь — обернуть объект в функцию и вернуть ссылку на локальный объект:

ApiManager& apiMng() {
    static ApiManager rc;
    return rc;
}

Локальный объект инициализируется при первом вызове функции (а при использовании C++11 также и потокобезопасным способом). Таким образом, порядок построения объектов с глобальным доступом можно упорядочить удобным способом.

Тем не менее, не используйте глобальные объекты. Они приносят больше вреда, чем пользы.

person Dietmar Kühl    schedule 17.12.2013
comment
Это неправильно ИМХО. Лучше использовать Q_GLOBAL_STATIC, чем собственный код, созданный вручную. - person lpapp; 18.12.2013
comment
Кроме того, Q_GLOBAL_STATIC является потокобезопасным, в отличие от вашей ручной версии. - person lpapp; 18.12.2013
comment
@LaszloPapp: вам может нравиться Qt, но 1. он не является частью стандарта C++, и 2. стандарт C++ гарантирует потокобезопасность моего подхода (см. 6.7 [stmt.dcl], параграф 4, чтобы убедиться в этом; соответствующее заявление находится ближе к концу этого параграфа). - person Dietmar Kühl; 18.12.2013
comment
Нет, стандарт C++ не гарантирует этого, по крайней мере, для C++ 98/03. Более того, это программа Qt, поэтому возражать против базовых функций QtCore и изобретать велосипед плохим и глючным способом неразумно. - person lpapp; 18.12.2013
comment
@LaszloPapp: я знаю, что C++98 и C++03 не говорят о потоках и не дают никаких гарантий безопасности потоков. Однако в конце 2011 года C++03 был заменен на C++11, который в настоящее время является актуальным стандартом. Поскольку вы делаете заявления о том, что мой код содержит ошибки, не могли бы вы указать точно, где мой код неверен и каким образом? - person Dietmar Kühl; 18.12.2013
comment
Я уже сделал. Пожалуйста, читайте внимательнее, что я пишу. Ваш код неверен. Он будет иметь неопределенное поведение для многопоточных приложений, которые должны работать там, где работает Qt. Это не предлагает решение Qt, которое работает с любым компилятором, который поддерживает Qt. Он глючит, на мой взгляд, он ошибочно помещен в эту ветку (поскольку это ветка Qt). Я понимаю, что вы не знали о том, что я написал, но это все-таки вопрос Qt. Qt наощупь более универсален, чем просто C++11, для которого не везде предполагается даже каждая фича. - person lpapp; 18.12.2013
comment
@LaszloPapp: я ясно вижу тег C++ в этом вопросе, и инициализация определенно правильная в C++. Я вижу, что вы предпочитаете другой подход, но мой код, безусловно, правильный C++. Пожалуйста, дайте мне знать, когда вы удалите тег C ++, потому что в этом случае мне кажется, что я должен удалить ответ. ... и обратите внимание, я прочитал именно то, что вы написали. Однако это никоим образом не делает мой код ошибочным. - person Dietmar Kühl; 18.12.2013
comment
Если вы не имеете ни малейшего представления о Qt, пожалуйста, прекратите публиковать сообщения в темах Qt. Ясно, что вы безрассудно предполагаете, что Qt — это примерно C++11, что очень далеко не так. Мы даже не включили тестирование C++11 в CI Qt. Я даже не смотрел стандарт. Я даже не могу сказать, что вы здесь правы, но бессмысленно обсуждать в ветке Qt, есть ли это в С++ 11 или нет. Qt более универсален, к счастью. Это все равно, что утверждать, что OpenGL 4 является текущим стандартом, поэтому каждое оборудование и драйвер должны его поддерживать. Вы далеко отсюда, ИМО. - person lpapp; 18.12.2013
comment
@LaszloPapp: обратите внимание, что я не публикую сообщения в темах Qt! Я ответил на вопрос с пометкой C++ и дал ответ C++. Я не собираюсь воздерживаться от ответа на вопрос о С++, потому что он может быть помечен какой-то технологией, о которой я, возможно, не знаю. Вы можете комментировать таким образом, что это не рекомендуется в конкретном контексте, но это не то, что вы сделали. Вместо этого вы решили заявить, что это неправильно, ИМХО, продолжая рекламировать что-то, недоступное для всех пользователей С++. Должен ли я потребовать, чтобы вы перестали комментировать вопросы по C++? - person Dietmar Kühl; 18.12.2013
comment
Обратите внимание, что я не публикую сообщения в темах Qt! -> Это поток Qt, помеченный так OP, обозначенный также некоторым кодом Qt. Вы не были в курсе технологии Qt, а вам сейчас рассказали о правильном пути ИМХО. Разве вы не должны быть счастливы, что узнали что-то новое сегодня? :) Скорее, вы пытаетесь защищаться, даже если вы неправы. Кроме того, это определенно не помечено как C++11, которое люди используют для вопросов C++11. Я не понимаю, почему вы так упорно защищаете неправильное решение. - person lpapp; 18.12.2013
comment
Q_GLOBAL_STATIC появился в Qt 5.1. Мне нужно что-то для Qt 4.8 (или в стандарте С++) - person Dibo; 18.12.2013
comment
@Dibo: Qt 4.8 все равно не будет поддерживать C++ 11 должным образом, поэтому в старом стандарте ничего нет, как написано. Более того, Q_GLOBAL_STATIC ** есть ** в Qt 4.8, просто он недокументирован. qt.gitorious.org/qt/ qt/источник/ - person lpapp; 18.12.2013
comment
Спасибо Ласло! я проверю это - person Dibo; 18.12.2013

Хорошей привычкой в ​​мире C++ было бы избегать глобальных объектов любой ценой - чем более локализован объект, тем он лучше.

Если вам абсолютно необходимо иметь глобальный объект, я думаю, что лучше всего было бы инициализировать объекты в пользовательском порядке в основном - чтобы четко указать порядок инициализации. Тот факт, что вы используете qt, является еще одним аргументом в пользу инициализации в main - вы, вероятно, захотите инициализировать QApplication (который требует argc и argv в качестве входных аргументов) перед любым другим QObject.

person Ilya Kobelevskiy    schedule 17.12.2013
comment
Вы не всегда можете инициализировать все в main. - person lpapp; 18.12.2013