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
?
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.
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With