Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RAII and factory design pattern?

Let's say I have Foo class:

struct Resource {
  void block();
  void unblock();
};
struct Foo {
   static Foo create() {
      Resource resource;
      resource.block();
      return Foo{resource};
    }
   ~Foo() { resource.unblock(); }
   void f() {}
private:
   Resource resource;
   Foo(Resource resource): resource(resource) {}
};

Am I right and there is no guarantee that ~Foo will be called only once in such block?

{
   Foo foo = Foo::create();
   foo.f();
}

If there is no guarantee, is it possible to fix somehow if using c++11 and move semantic? For example not call unblock_resource in moved foo, but I am not sure is there guarantee to use move constructor/operator= in return from Foo::create?

like image 975
user1244932 Avatar asked Dec 19 '22 02:12

user1244932


2 Answers

Copy elision won't help you, since it is an optimization and may or may not be applied.

Move semantics do help, and you get guaranteed moves in function return of local variables. But that means you have to write the move constructor, and you have to modify the destructor so that it doesn't unlock the resource of an object that was moved from.

like image 194
Sebastian Redl Avatar answered Dec 24 '22 01:12

Sebastian Redl


Not sure about how this is related to the factory pattern, but to answer your question "Am I right and there is no guarantee that ~Foo will be called only once in such block?":

Avoiding copying/moving objects used as return values (i.e. return value optimization, especially named return value optimization) is permitted, but not guaranteed:

Under the following circumstances, the compilers are permitted, but not required to omit the copy- and move- (since C++11)construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

If a function returns a class type by value, and the return statement's expression is the name of a non-volatile object with automatic storage duration, which isn't a function parameter, or a catch clause parameter, and which has the same type (ignoring top-level cv-qualification) as the return type of the function, then copy/move (since C++11) is omitted. When that local object is constructed, it is constructed directly in the storage where the function's return value would otherwise be moved or copied to. This variant of copy elision is known as NRVO, "named return value optimization".

One way could be to control locking/unlocking resources in the move constructor and the destructor, just as you mentioned in your question.

Another way could be to make use of shared_ptr, such that creation and deletion of your Foo-object is managed by an RAII-style shared_ptr wrapper. There is just one tricky thing if you want to keep the Foo-constructor private, since make_shared cannot deal with private constructors. To overcome this, you could declare a public constructor taking a parameter of a private data type as parameter. A little bit ugly, and maybe a little bit clumsy due to the shared_ptr-wrapper. But maybe it's at least some inspiration:

struct Foo {
private:

    struct private_dummy {};

public:
    static shared_ptr<Foo> create() {
        shared_ptr<Foo> foo = make_shared<Foo>(private_dummy{});
        return foo;
    }
    ~Foo() { cout << "deleted."; }
    Foo(struct private_dummy x) { cout << "created."; }
};

void test() {

    shared_ptr<Foo> foo = Foo::create();

}

int main() {

    test();

    //Foo notOK();
}
like image 23
Stephan Lechner Avatar answered Dec 24 '22 02:12

Stephan Lechner