Глобален обект и ред на създаване

Все още уча C++. Имам един проблем. Да кажем, че вашият проект има глобален обект, който винаги съществува, например ApiManager и всички други модули имат достъп до него (от #include). Засега го правя по:

Заглавие:

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

extern ApiManager apiMng;

източник:

ApiManager apiMng;

Проблемът е, че и други обекти трябва да имат достъп при инициализиране и забелязах, че C++ глобалните обекти се създават по азбучен ред. Чудя се как се справяте с това? Има ли някакъв трик за това? Например в света на 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 accessors обикновено са за предпочитане пред директния достъп до данни и това важи още повече за глобалните данни.) Като разширение, липсата на контрол на достъпа силно възпрепятства постигането на сигурност в ситуации, в които може да пожелаете да стартирате ненадежден код (като работа с плъгини на трети страни). Неявно свързване -- Програма с много глобални променливи често има тесни връзки между някои от тези променливи и връзки между променливи и функции. Групирането на свързани елементи в сплотени единици обикновено води до по-добри програми.

Проблеми с паралелността - ако глобалните могат да бъдат достъпни от множество нишки на изпълнение, синхронизирането е необходимо (и твърде често пренебрегвано). При динамично свързване на модули с глобали, съставената система може да не е безопасна за нишки, дори ако двата независими модула, тествани в десетки различни контексти, са безопасни.

Замърсяване на пространството от имена -- Глобалните имена са налични навсякъде. Може несъзнателно да използвате глобален, когато мислите, че използвате локален (чрез правописна грешка или забравяне да декларирате локалния) или обратното. Също така, ако някога трябва да свържете заедно модули, които имат едни и същи имена на глобални променливи, ако имате късмет, ще получите грешки при свързването. Ако нямате късмет, линкерът просто ще третира всички употреби на едно и също име като един и същ обект. Проблеми с разпределението на паметта -- Някои среди имат схеми за разпределяне на памет, които правят разпределянето на глобални стойности трудно. Това е особено вярно в езиците, където "конструкторите" имат странични ефекти, различни от разпределението (защото в този случай можете да изразите опасни ситуации, при които два глобала взаимно зависят един от друг). Също така, когато динамично свързвате модули, може да не е ясно дали различните библиотеки имат свои собствени екземпляри на глобали или дали глобалите са споделени.

Тестване и ограничаване - източникът, който използва глобални стойности, е малко по-труден за тестване, защото човек не може лесно да настрои "чиста" среда между изпълненията. По-общо, източникът, който използва глобални услуги от всякакъв вид (напр. четене и запис на файлове или бази данни), които не са изрично предоставени на този източник, е труден за тестване по същата причина. За комуникиращите системи възможността за тестване на системни инварианти може да изисква стартиране на повече от едно „копие“ на система едновременно, което е силно възпрепятствано от всяко използване на споделени услуги – включително глобална памет – които не са предоставени за споделяне като част от теста .

Като цяло, избягвайте глобалните променливи като основно правило. Ако трябва да ги имате, моля, използвайте 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
Това е грешно IMHO. По-добре е да използвате 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 не говорят за нишки и не дават никакви гаранции относно безопасността на нишките. Въпреки това, C++03 беше заменен в края на 2011 г. от 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 дали C++11 го има или не. Qt е по-гъвкав, за щастие. Това е все едно да твърдите, че OpenGL 4 е текущият стандарт, така че всеки хардуер и драйвер трябва да го поддържат. Вие сте на мили тук IMO. - person lpapp; 18.12.2013
comment
@LaszloPapp: Имайте предвид, че не публикувам в теми на Qt! Отговорих на въпрос с етикет C++ и дадох отговор на C++. Няма да се въздържа от отговор на въпрос за C++, защото може да е маркиран с някаква технология, за която може да не знам. Вие сте свободни да коментирате по начин, който показва, че това не е препоръчителният подход в конкретен контекст, но това не е това, което сте направили. Вместо това сте избрали да заявите, че е грешно IMHO, продължавайки да рекламирате нещо, което не е достъпно за всички потребители на C++. Трябва ли да изисквам да спрете да коментирате C++ въпроси? - person Dietmar Kühl; 18.12.2013
comment
Имайте предвид, че не публикувам в теми на Qt! -› Това е Qt нишка, маркирана така от OP, посочена също и от някакъв Qt код. Не сте знаели за технологията Qt и сега ви беше казано за правилния начин IMHO. Не трябва ли да се радвате, че сте научили нещо ново днес? :) По-скоро гледаш да си отбранителен, дори и да грешиш. Освен това, това определено не е маркирано като C++11, което хората използват за C++11 въпроси. Не разбирам защо толкова упорито защитавате грешно решение. - person lpapp; 18.12.2013
comment
Q_GLOBAL_STATIC беше въведен в Qt 5.1. Трябва ми нещо за Qt 4.8 (или в C++ стандарт) - person Dibo; 18.12.2013
comment
@Dibo: Qt 4.8 така или иначе няма да поддържа правилно C++11, така че няма нищо в стария стандарт, както е написано. Освен това Q_GLOBAL_STATIC ** е ** наличен в Qt 4.8, просто е недокументиран. qt.gitorious.org/qt/ qt/source/ - person lpapp; 18.12.2013
comment
Благодаря Ласло! Ще проверя това - person Dibo; 18.12.2013

Добър навик в света на C++ би бил да се избягват глобалните обекти на всяка цена - колкото по-локализиран е обектът, толкова по-добър е той.

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

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