Как можно применять RAII для членов класса, которым требуется расширенная инициализация?

Насколько я понимаю идиому RAII при применении к ресурсам, необходимым классу (и, пожалуйста, поправьте меня, если я ошибаюсь), класс, которому требуется ресурс, должен определять член соответствующего типа, и его деструктор будет вызываться автоматически когда экземпляр класса using уничтожается, например:

class Skybox
{
    public:
        Skybox() : tex_(...) {}

    private:
        Texture tex_;
};

Помимо использования интеллектуального указателя для выделения ресурса в куче, как можно применить этот шаблон, если член ресурса требует выполнения некоторого кода в конструкторе Skybox перед инициализацией ресурса? Например:

class Skybox
{
    public:
        Skybox(const std::string& fileName);

    private:
        Texture tex_;
}

Skybox::Skybox(const std::string& fileName)
{
    // read stuff from skybox initialization file
    // including various texture parameters such as texture file
    ...
    // initialize tex_ based on information read above
}

Обновление: класс Texture требует, чтобы вся инициализация выполнялась в его конструкторе (т.е. метод Texture::Init() недоступен)


person Dan Nestor    schedule 21.11.2011    source источник
comment
Допускает ли класс Texture более позднюю инициализацию, например tex_.init(...)?   -  person Kerrek SB    schedule 21.11.2011
comment
Нет. Я обновлю текст вопроса.   -  person Dan Nestor    schedule 21.11.2011
comment
Есть ли какие-либо предварительные вычисления, которые нужно использовать для чего-либо, кроме инициализации tex? Если нет, просто создайте конструктор для текстуры, который принимает имя файла и выполняет всю эту обработку как часть своей собственной инициализации.   -  person matthias    schedule 21.11.2011
comment
Вы здесь странно связаны. Skybox не нужно ничего знать о путях к файлам. Skybox нужно передавать только в Texture. Позвольте Texture или другой абстракции обрабатывать ваши знания о файловой системе.   -  person Tom Kerr    schedule 22.11.2011
comment
Извините, мне следовало быть более откровенным. Я также читаю файл инициализации скайбокса. Я обновлю вопрос.   -  person Dan Nestor    schedule 22.11.2011


Ответы (4)


Оберните код инициализации в функцию и используйте эту функцию (член или не член, статический или нестатический, в зависимости от ситуации) для инициализации переменной-члена:

Texture Skybox::init_tex(std::string const& fileName) {
  // read stuff from file, including textureFile
  // initialize result
  return Texture(...);
}

Skybox::Skybox(std::string const& fileName):
  tex_(init_tex(fileName))
{ }

Функция инициализации, вероятно, должна быть статической функцией. Если это не так, будьте осторожны, чтобы не использовать какие-либо члены, которые еще не были инициализированы - вы вызываете init_tex для еще не полностью инициализированного экземпляра Skybox.

person Rob Kennedy    schedule 21.11.2011
comment
Хороший. Должна ли это быть статическая функция на всякий случай? - person Kerrek SB; 21.11.2011
comment
Использование нестатических функций-членов в конструкторе требует большой осторожности и нелокальных гарантий. Статическая функция-член избавит от всех этих забот. - person Kerrek SB; 21.11.2011
comment
Я не совсем согласен с этим. Загрузка Texture не должна быть связана с Skybox. Я бы предпочел иметь для этого бесплатную функцию. - person pmr; 21.11.2011
comment
@Pmr, если бы текстура могла быть создана исключительно из имени файла, то мы могли бы ожидать, что у нее будет конструктор, принимающий имя файла. Вместо этого кажется, что есть некоторые специфические для скайбокса аспекты того, как текстура загружается из файла - возможно, текстура встроена в специальный формат файла скайбокса. - person Rob Kennedy; 21.11.2011
comment
@RobKennedy Думаю, нам нужно больше узнать о задействованных классах. Но поскольку OP принял ваш ответ, похоже, вы правы. - person pmr; 22.11.2011

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

Tex tex_from_file(const std::string&) {
  // ...
}

class Skybox {
  Skybox(const std::string& s) : tex_(tex_from_file(s)) {}
};

Еще лучше был бы Skybox, созданный из Tex объекта. Однако для этого требуется, чтобы Tex можно было копировать или перемещать. Если это не так, правильным решением может быть возврат std::unique_ptr<Tex>.

person pmr    schedule 21.11.2011

Используя функции C ++ 11 (вариативные шаблоны и идеальную пересылку), этого можно достичь с помощью конструктора шаблона:

#include <utility>
template<class T>
class raii_wrapper
{
    public:

template<typename... Arg>
    raii_wrapper(Arg&&... args) : obj(std::forward<Arg>(args)...) {}

    private:
        T obj;
};

struct foo
{
    foo(){}
};

struct foo_1
{
    foo_1(int){}
};

struct foo_2
{
    foo_2(int,int&){}
};

int main()
{
    raii_wrapper<foo> f;
    raii_wrapper<foo_1> f1(1);
    int i(3);
    raii_wrapper<foo_2> f2(1,i);
    return 0;
}

В C ++ 03/98 конструктор шаблонов по-прежнему является решением (но ускорение должно помочь для вариативного шаблона и передачи аргументов). См. Реализации таких функций, как make_share_ptr.

person MCAL    schedule 21.11.2011
comment
Не могли бы вы изменить свой код так, чтобы он напрямую относился к классам Skybox и Texture в примере? - person Rob Kennedy; 21.11.2011

Если класс Texture имеет конструктор по умолчанию и поддерживает подкачку, вы можете инициализировать ресурс с помощью локальной переменной и поменять местами в конце конструктора.

Skybox::Skybox(const std::string& fileName)
{
    Texture localTex(fileName);
    //...
    tex_.swap(localTex);
}
person Mark Ransom    schedule 21.11.2011