Well, here's the thing, there is actually no need at all for init
in your example. The language rules already dictate default member initializer go first, then what ever you do in the member initializer list. The members are initialized in declaration order. So you could really just define each c'tor as
A() upDBHandle(open_database(a1, a2, a3)) { }
explicit A(int i):a1(i), upDBHandle(open_database(a1, a2, a3)) {}
explicit A(double d):a2(d), upDBHandle(open_database(a1, a2, a3)) {}
explicit A(std::string s):a3(std::move(s)), upDBHandle(open_database(a1, a2, a3)) {}
A(int i, double d, std::string s) : a1(i), a2(d), a3(std::move(s)), upDBHandle(open_database(a1, a2, a3)) {}
And that's it. Some may say it's not good, since changing the class declaration can make it break. But compilers are pretty good at diagnosing this. And I belong to the school of thought which says a programmer should know what they are doing, and not just build code by happy coincidence.
You can even do that pre-C++11. Sure, you don't have default member initializers, and would have to repeat the default values for each member (possibly name them somehow first, to avoid magic numbers), but that isn't related to the issue of initializing the member that depends on their initial value.
Advantages over init
?
- Support for
const
members, however rare they may come.
- Support for objects with no default state. Those cannot be default initialized and wait until
init
is called.
- No need to add superfluous functions to your class, that are unrelated to its function.
Now, if the initialization code for the member is not trivial, you can still put it in a function. A free function, that is hopefully static to your classes' translation unit. Like so:
static std::unique_ptr<DatabaseHandle> init_handle(int a1, double a2, std::string const& a3) {
// do other stuff that warrant a function block
return open_database(a1, a2, a3);
}
A::A() upDBHandle(init_handle(a1, a2, a3)) { init(); }
A::A(int i):a1(i), upDBHandle(init_handle(a1, a2, a3)) {}
A::A(double d):a2(d), upDBHandle(init_handle(a1, a2, a3)) {}
A::A(std::string s):a3(std::move(s)), upDBHandle(init_handle(a1, a2, a3)) {}
A::A(int i, double d, std::string s) : a1(i), a2(d), a3(std::move(s)), upDBHandle(init_handle(a1, a2, a3)) {}
That also means that you can have several of those for different members. So now the concern of initializing the members is more spread out.
Removing the need for the many c'tors can actually be done with something that Fred Larson suggested in his comment to your post.