2016-04-21 2 views
0

Я разрабатываю библиотеку объектов и функций и имею файл заголовка, который называется 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 

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

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

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

Спасибо!

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

ответ

0

Следуя предложениям Варшавчика, я внес несколько изменений. Во-первых, я заменил заголовок 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 

база.каст

#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 вместо «Foo»

2

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

static bool isInit = false; 

static initializer_struct init; 

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

initializer_struct(){ 

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

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

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

+0

Я немного обернулся; Я должен объявлять глобальные объекты статическими членами класса? Если я сделаю их членами класса, разве они перестанут быть глобальными? – AndrewCox

+0

Нет, это неклассические члены класса, которые являются частью каждого экземпляра каждого класса. Элементы класса 'static' являются глобальными. Посмотрите свою любимую книгу на C++ для более подробного обсуждения членов класса 'static' и правильного способа объявить и определить их. –

+0

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

0

Вы почти есть, просто переместить статический 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; 

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

Это будет выглядеть следующим образом:

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 от указателя к j усть объект, объявив его как объект в singleton.cpp и изменения GetInstance() прототип:

initializer_struct &GetInstance() { return _instance; } 

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