Dan Nestor
Dan Nestor

Reputation: 2531

How can RAII be applied for class members that require extended initialization?

As far as I understand the RAII idiom when applied to resources needed by a class (and please correct me if I'm wrong), a class that requires a resource should define a member of the appropriate type, and its destructor will be called automatically when the using class instance is destroyed, like this:

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

    private:
        Texture tex_;
};

Apart from using a smart pointer to allocate the resource on the heap, how can this pattern be applied if the resource member requires some code to be executed in the Skybox constructor, before the initialisation of the resource? For example:

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
}

Update: the Texture class requires all initialization to be performed in its constructor (i.e. no Texture::Init() method is available)

Upvotes: 4

Views: 1480

Answers (4)

Mark Ransom
Mark Ransom

Reputation: 308206

If the Texture class has a default constructor and supports swapping, you can initialize the resource with a local variable and swap it at the end of the constructor.

Skybox::Skybox(const std::string& fileName)
{
    Texture localTex(fileName);
    //...
    tex_.swap(localTex);
}

Upvotes: 0

Rob Kennedy
Rob Kennedy

Reputation: 163287

Wrap the initialization code into a function, and use that function (member or non-member, static or non-static, as appropriate) to initialize the member variable:

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))
{ }

The initialization function should probably be a static function. If it isn't, be careful not to use any members that haven't been initialized yet — you're calling init_tex on a not-yet-fully initialized Skybox instance.

Upvotes: 10

pmr
pmr

Reputation: 59811

Maybe you should encapsulate the creation of a texture into a free function as the reading of the file seems unrelated to the Skybox and could possibly useful somewhere else. I guess another name for this is Factory.

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

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

Even nicer would be a Skybox constructible from a Tex object. However, this requires Tex to be copy or move constructible. If this isn't the case a proper workaround could be to return a std::unique_ptr<Tex>.

Upvotes: 2

MCAL
MCAL

Reputation: 1

Using C++11 features (variadic templates and perfect forwarding), this can be achieved thanks to a template constructor :

#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;
}

In C++03/98, template constructor is still the solution (but boost shall help for variadic template and argument passing). See implementations of functions like make_share_ptr.

Upvotes: 0

Related Questions