Say you have this thing:
struct Foo {
// some fields
};
void foo_init(Foo* f) {
// init Foo's fields
}
void foo_deinit(Foo* f) {
// deinit Foo's fields
}
And let's say you use it like this:
int main() {
Foo f1;
foo_init(&f1);
if (some_error) {
return 1;
}
Foo f2;
foo_init(&f2);
if (some_other_error) {
return 1;
}
foo_deinit(&f1);
foo_deinit(&f2);
return 0;
}
The code above is wrong.
If some_error
or some_other_error
happen, the Foo
s will not be deinit'ed.
You have to you make sure that in every return path the Foo
s will be deinit'ed:
int main() {
Foo f1;
foo_init(&f1);
if (some_error) {
foo_deinit(&f1);
return 1;
}
Foo f2;
foo_init(&f2);
if (some_other_error) {
foo_deinit(&f2);
foo_deinit(&f1);
return 1;
}
foo_deinit(&f1);
foo_deinit(&f2);
return 0;
}
But it's easy to forget to add the foo_deinit
in every return path for every Foo
.
Or even worse, there can be a lot of Foo
-like resources to deinit in every return path.
Then the problem gets even messier.
So is there a better way?
Answer: std::unique_ptr
int main() {
Foo f1;
foo_init(&f1);
std::unique_ptr<Foo, void(*)(Foo*)> f1_ptr{&f1, &foo_deinit};
if (some_error) {
return 1;
}
Foo f2;
foo_init(&f2);
std::unique_ptr<Foo, void(*)(Foo*)> f2_ptr{&f2, &foo_deinit};
if (some_other_error) {
return 1;
}
return 0;
}
This will guarantee that foo_deinit
will be called whenever
the scope in which the given Foo
was defined is finished.
You declare your Foo
, init it and then create a unique_ptr
for it.
Then you're done, no need to worry if it will be deinit'ed in every path.
There is an extra benefit.
The order of deinitialization must be the reverse order of initialization
(i.e. if f1
is init'ed before f2
, then f2
must be deinit'ed before f1
).
In the first version, you have to keep track of this.
In the second version, it's guaranteed that the order is correct.