Reputation: 4453
I know that additional initialization methods are evil, as they leave a very nasty option for having object half-constructed and as result all methods needs to check for this. But what about this situation?
class config;
class cfg_item final
{
private:
friend class config;
cfg_item(std::weak_ptr<config> owner) : owner(owner) { }
std::weak_ptr<config> owner;
}
class config final : private std::enable_shared_from_this<config>
{
public:
config()
{
items.emplace(std::make_shared<cfg_item>(weak_from_this())); // Will crash!
}
private:
std::vector<std::shared_ptr<cfg_item>> items;
}
int main(int argc, char * argv[])
{
std::shared_ptr<config> cfg = std::make_shared<config>();
}
I KNOW WHY IT CRASHES. The std::shared_ptr
in the main is not yet initialized with shared pointer to config instance, so constructor does not know how to make weak_from_this
and just raises std::bad_weak_ptr
exception because there are no valid std::shared_ptr
pointing to this
at constructor's call time.
The question is: how can I avoided the whole thing? I believe the only way I see would be to add separate initialization method, which is evil as I've already mentioned...
As note about real code: the constructors loads cfg_item
from external source. It is assumed that all cfg_item
s are available for the entire lifetime of config
. The weak pointers back to config
are mandatory, as cfg_item
must push all changes done to it back to config
to save to external source
Upvotes: 2
Views: 323
Reputation: 76297
If you look at the answers to this question, there are strong arguments why an external initialization function is necessary. However, you rightfully write
I know that additional initialization methods are evil, as they leave a very nasty option for having object half-constructed and as result all methods needs to check for this.
it's possible to reduce this problem. Say you have a class foo
, with the protocol that each time a foo
object is constructed, foo::init()
needs to be called. Obviously, this is a brittle class (client code will eventually omit calls to init()
).
So, one way is to make the (non-copy / non-move) constructors of foo
private
, and create a variadic static factory method that creates objects, then calls init()
:
#include <utility>
class foo {
private:
foo() {}
foo(int) {}
void init() {}
public:
template<typename ...Args>
static foo create(Args &&...args) {
foo f{std::forward<Args>(args)...};
f.init();
return f;
}
};
In the following code
template<typename ...Args>
static foo create(Args &&...args) {
foo f{std::forward<Args>(args)...};
f.init();
return f;
}
note that this single method can be used for all constructors, regardless of their signature. Furthermore, since it is static
, it is external to the constructor, and doesn't have the problems in your question.
You can use it as follows:
int main() {
auto f0 = foo::create();
auto f1 = foo::create(2);
// Next line doesn't compile if uncommented
// foo f2;
}
Note that it's impossible to create an object without this method, and the interface doesn't even contain init
.
Upvotes: 2