Повторная инициализация структуры в заголовке

Я занимаюсь разработкой библиотеки объектов и функций, и у меня есть файл заголовка, названный здесь super.hpp, который содержит некоторые задачи инициализации.

super.hpp

#ifndef H_INIT
#define H_INIT

#include <iostream>
#include <string>

static bool isInit = false;

struct settings_struct{
std::string path = "foo";
void load(){ path = "bar"; }
};

struct initializer_struct{
settings_struct settings;

initializer_struct(){
if(!isInit){
std::cout << "Doing initialization\n";
settings.load();
isInit = true;
}
// settings.load();
}//====================

~initializer_struct(){
if(isInit){
std::cout << "Doing closing ops\n";
isInit = false;
}
}
};

static initializer_struct init; // static declaration: only create one!

#endif

Мое намерение с этим заголовком состоит в том, чтобы создать initializer_struct объект один раз; когда она построена, эта структура выполняет несколько действий, которые устанавливают флаги и настройки для всей библиотеки. Одним из этих действий является создание структуры настроек, которая загружает настройки из файла XML; это действие также должно происходить только один раз при создании структуры init, поэтому переменные (здесь path) сохраняются из файла настроек. super.hpp заголовок включен во все объекты в библиотеке, потому что разные объекты используются в разных мощностях, то есть нет способа предсказать, какие из них будут использоваться в приложении, поэтому я включаю super.hpp заголовок во всех них, чтобы гарантировать, что это называется независимо от того, какие объекты используются.

Моя проблема заключается в следующем: когда я включаю super.hpp в нескольких классах / объектах, которые все загружены основным приложением, структура init кажется, что инициализируется заново, и переменные устанавливаются, когда settings_struct построен перезаписаны значениями по умолчанию. Чтобы увидеть это в действии, рассмотрите следующие дополнительные файлы:

test.cpp

#include "classA.hpp"#include "classB.hpp"#include <iostream>

int main(int argc, char *argv[]){
(void) argc;
(void) argv;

classA a;
classB b;

std::cout << "Settings path = " << init.settings.path << std::endl;
std::cout << "Class A Number = " << a.getNumber() << std::endl;
std::cout << "Class B Number = " << b.getInteger() << std::endl;
}

classA.hpp

#ifndef H_CLASSA
#define H_CLASSA

class classA{
private:
double number;

public:
classA() : number(7) {}
double getNumber();
};

#endif

classA.cpp

#include "super.hpp"#include "classA.hpp"
double classA::getNumber(){ return number; }

classB.hpp

#ifndef H_CLASSB
#define H_CLASSB

class classB{
private:
int number;

public:
classB() : number(3) {}
int getInteger();
};

#endif

classB.cpp

#include "super.hpp"#include "classB.hpp"
int classB::getInteger(){ return number; }

Чтобы скомпилировать и запустить пример,

g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classA.cpp -o classA.o
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classB.cpp -o classB.o
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic classA.o classB.o test.cpp -o test.out
./test.out

Я ожидаю, что вывод test.out будет следующим:

Doing initialization
Settings path = bar
Number = 7
Doing closing ops

Однако, когда я запускаю это, я вместо этого получаю «Параметры пути = foo». Таким образом, мой вывод заключается в том, что initializer_struct, init, строится более одного раза. Первый раз, логическое isInit ложно, и структура настроек load наборы функций path в бар.» Для всех последующих инициализаций isInit верно, поэтому load функция не вызывается снова, и кажется, что значения переменных из неинициализированных settings (Т.е. path = "foo") перезаписать ранее загруженные значения, следовательно вывод init.settings.path в test.cpp,

Почему это? Почему init объект создается каждый раз, когда заголовок включен? Я бы подумал, что включенные охранники будут предотвращать повторный вызов кода заголовка. Если я сделаю init переменная в test.hpp нестатическая переменная, затем создается несколько копий, и выходные данные выводят несколько итераций «Выполнение инициализации» и «Выполнение операций закрытия». Кроме того, если я раскомментирую settings.load() вызов функции вне условного оператора в initializer_struct() конструктор, то вывод дает путь настройки «бар». Наконец, снятие включения super.hpp от classA.cpp приводит к значению пути «bar», что еще больше подтверждает мою гипотезу о том, что множественные включения test.hpp привести к нескольким вызовам конструктора.

Я хотел бы избежать settings.load()' called for every object that includessuper.hpp` — поэтому я поместил команду в условном выражении. Какие-нибудь мысли? Как убедиться, что файл настроек читается только один раз и что загруженные значения не перезаписываются? Это абсолютно тупой метод для установки некоторых флагов и настроек, которые использует моя библиотека? Если да, есть ли у вас какие-либо предложения, чтобы сделать процессы более простыми и / или более элегантными?

Спасибо!

редактировать: Обновлено, чтобы включить два класса объектов, чтобы ближе представить мою более сложную установку

0

Решение

В вашем заголовочном файле вы определяете эти static глобальные объекты:

static bool isInit = false;

static initializer_struct init;

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

initializer_struct(){

Этот конструктор, тем не менее, будет определен в вашем приложении только один раз. Компилятор фактически скомпилирует конструктор в каждом модуле перевода, который включает эти заголовочные файлы, и в каждом модуле перевода конструктор будет использовать static глобальный объект из его единицы перевода.

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

Есть способ сделать это правильно: объявить static глобальные объекты как static вместо этого члены класса, а затем создать экземпляр этих static глобальные объекты в одном из блоков перевода. Блок перевода с вашим main() это отличный выбор. Тогда будет единственная копия всего.

2

Другие решения

Следуя предложениям Варшавчика, я внес несколько изменений. Во-первых, я заменил super.hpp заголовок с очень базовым классом, который могут расширять все объекты в моей библиотеке:

base.hpp

#ifndef H_BASE
#define H_BASE

#include <iostream>
#include <string>

struct settings_struct{
settings_struct(){
std::cout << "Constructing settings_struct\n";
}
std::string path = "foo";
void load(){ path = "bar"; }
};

struct initializer_struct{
settings_struct settings;

initializer_struct(){
std::cout << "Constructing initializer_struct\n";
}

~initializer_struct(){
std::cout << "Doing closing ops\n";
}

void initialize(){
std::cout << "Doing initialization\n";
settings.load();
}
};

class base{
public:
static initializer_struct init;
static bool isInit;

base();
};

#endif

base.cpp

#include "base.hpp"
initializer_struct base::init;
bool base::isInit = false;

base::base(){
if(!isInit){
init.initialize();
isInit = true;
}
}

Другие файлы остаются более или менее такими же, с некоторыми изменениями. Во-первых, оба classA а также classB продлить base учебный класс:

class classA : public base {...}
class classB : public base {...}

Теперь, когда любой из объектов создается, вызывается конструктор базового класса, который инициализирует настройки и другие переменные. один раз. И то и другое isInit а также init являются статическими членами base класс, поэтому все объекты, которые включают в себя base заголовок или расширить base Объект имеет доступ к своим ценностям, что соответствует моим потребностям. Эти значения доступны через

base::init.settings.path

и выход теперь то, что я ожидаю и хочу, чтобы это было, с Settings path = bar вместо «фу»

0

У вас почти это есть, просто переместите static isInit, чтобы он был статическим членом вашего класса, и перенесите создание экземпляра init в модуль перевода. Это будут следующие файлы:

super.hpp

#ifndef H_INIT
#define H_INIT

#include <iostream>
#include <string>

struct initializer_struct{
static bool isInit;

struct settings_struct{
std::string path = "foo";
void load(){ path = "bar"; }
} settings;

initializer_struct(){
if(!isInit){
std::cout << "Doing initialization\n";
settings.load();
isInit = true;
}
// settings.load();
}//====================

~initializer_struct(){
if(isInit){
std::cout << "Doing closing ops\n";
isInit = false;
}
}
};

extern initializer_struct init; // extern declaration, instantiate it in super.cpp

#endif

super.cpp

#include "super.hpp"
bool initializer_struct::isInit = false;
initializer_struct init;

Однако вам лучше использовать одноэлементный шаблон. Используя шаблон синглтона, вы убедитесь, что создан только один экземпляр вашего класса. Вы можете получить горстку информации здесь: C ++ Singleton дизайн шаблона

Это будет выглядеть так:

singleton.hpp

#pragma once

class initializer_struct{
public:
struct settings_struct{
std::string path = "foo";
void load(){ path = "bar"; }
} settings;

static initializer_struct *GetInstance() {
if (_instance == NULL) {
_instance = new initializer_struct();
}
return _instance;
}
~initializer_struct(){
}
private:
initializer_struct(){
if(!isInit){
std::cout << "Doing initialization\n";
settings.load();
isInit = true;
}
}

static initializer_struct *_instance;
}

singleton.cpp

#include "singleton.hpp"
initializer_struct *initializer_struct::_instance = NULL;

Вы даже можете пойти на инициализацию при загрузке, изменив _instance с указателя на просто объект, объявив его как объект в singleton.cpp и изменив прототип GetInstance () на:

initializer_struct &GetInstance() { return _instance; }

Однако с последним остерегайтесь статического порядка инициализации фиаско (http://yosefk.com/c++fqa/ctors.html#fqa-10.12). Короче говоря, вы можете использовать последний подход, если инициализация вашего класса НЕ зависит от инициализации другого класса, поскольку вы не знаете, какой из них будет инициализирован первым.

0
По вопросам рекламы [email protected]