What can I do when a error occurs in Object's constructor?

Started by
89 comments, last by phresnel 13 years, 9 months ago
Quote:Original post by Burnhard
The way I'm using them comes from the following principles:

(1) Never use "delete"
(2) Only use "addref" or "release" in a custom allocator/deallocator
(3) Only ever use "new" in a class factory
(4) Every class has an abstract interface
(5) Every class has an implementation derived from the abstract interface
(6) The class factory returns instances of the abstract interface


This is the typical IOC/DI approach that has been universally proven to be a solid way to scale the design.

The only thing where it deviates is that it doesn't avoid constructors - it merely requires that no work beyond immediate assignment is performed in them, something which cannot fail. I'm not so sure about using init() for reinitialization, since I've found it over-complicates the invariant and pre-/post-condition verification - it's easier to just drop the instance and ask for a new one. YMMV.

This was long ago
">presented for Java, but it applies to C++ and to a degree even to C (with different syntax). WinAPI is an example of similar approach, where caller is responsible for providing properly constructed and allocated members.


The only thing I dislike about the above is IF/Impl separation. C++ has the advantage of free functions, so much of the logic can be expressed using free functions only, operating on elementary containers and PODs. It doesn't sacrifice anything (just as testable and extensible), but requires considerably less syntactic bloat. May require a slight shift in design approach to grok it.

Another interesting property of this approach, especially in C/C++ is that it gracefully handles the global/singleton issues. Since no instance may be requested directly, factory is the only place responsible for actual allocation, the code remains independent of usual gotchas since logic is never hard-coded to any specific instance so the design doesn't become locked in.
Advertisement
Quote:Original post by Burnhard
Consider the following:

*** Source Snippet Removed ***

You now have 10 zombie A's. You'll need to "initialise" them, won't you? Or at least pass in a "template" object to copy in. Stating that initialisation can only occur in a constructor is too dogmatic in my view. STL containers require (on the whole) objects to be default constructable. When the class is non-trivial, they often aren't.


Frankly, this is a silly example. Calling resize() on a vector of objects that are not in a fully constructed state after a no-params constructor is fundamentally wrong. It is simply not an appropriate operation on types that require parameters to be fully constructed. Clearly one should be push_back()ing them with the appropriate information.

In reality, if an object requires parameters in order to be fully constructed, it should not have a no-params constructor anyway, in which case the compiler would flag the resize() as an invalid operation.

This seems to me to be like de-referencing a null pointer as an parameter to a function taking a reference, then blaming the function for not checking - the error is simply elsewhere.
Quote:I think the problem here is you don't like my approach only because it's different to yours.

I think the problem here is that you take criticism personally. You've been advocating an overly prescriptive use of RAII which focuses too narrowly on shared_ptr. You've repeatedly refused to engage with the criticism, instead preferring to pretend that the use of smart pointers and RAII in general is being criticised.

The purpose of RAII is to remove the possibility of resource leaks, the purpose of giving base classes private copy constructors and assignment operators is the remove the possibility of object slicing, and the purpose of giving a base class a virtual destructor is to remove the possibility of invoking undefined behaviour by calling the destructor using virtual dispatch.

Your entire argument revolves around the fact that you've never "needed" to use virtual destructors or any kind of smart pointer other than shared_ptr. Plenty of successful and robust software was written in pre-standardisation C++ without the use of RAII at all - they didn't "need" RAII, but it would have made their work considerably easier and their code more maintainable. Just as correctly using smart pointers to convey information about the lifetime of the object would make your work easier (and, by happy coincidence, more performant).

Quote:Preferring not to sprinkle new, delete, addref or release in my code is not "niche", it's sensible. At least I think so. The question is how to achieve this goal? I've outlined one way of doing so

How to achieve that goal is easy - RAII. The way you're using shared_ptr everywhere restricts flexibility. You've previously denied that you lose flexibility, but I'd classify having to seperate initialisation from construction as a loss of flexibility. Why do you have to seperate initialisation from construction? Because you use shard_ptr and shared_from_this in places where it's innapropriate.



Quote:I think I can safely add another rule to my set of rules: prefer not to perform complex/potentially error-prone allocations in constructors. That is to say, first phase: regular constructor, second phase: operations that can fail. Then the question as to what to do when a constructor fails (the topic of this thread) is moot, because you guarantee that it never will.


All you've done is to replace the question of what to do when a constructor fails with what to do when initialize fails.
Quote:
Then you're wrong. The way I'm using them comes from the following principles:

(1) Never use "delete"
(2) Only use "addref" or "release" in a custom allocator/deallocator
(3) Only ever use "new" in a class factory
(4) Every class has an abstract interface
(5) Every class has an implementation derived from the abstract interface
(6) The class factory returns instances of the abstract interface


Those principles are good - but drawing "only use shared_ptr, don't use virtual destructors and always seperate construction from initialization" from those principles is absurd.
Quote:Original post by Nitage
Why do you have to seperate initialisation from construction? Because you use shard_ptr and shared_from_this in places where it's innapropriate.

Wait, what? Why would it be inappropriate to want to use shared_from_this in the constructor?
Quote:Wait, what? Why would it be inappropriate to want to use shared_from_this in the constructor?


Calling it from a constructor strongly couples the class to shared_ptr, to the extent that it can only be owned by a shared_ptr - can't even be allocated on the stack. It also creates some lifetime issues that can be a bit tricky to manage (and yes Burnhard, shared_from_this can safely be used from a constructor).
Quote:Original post by Nitage
Calling it from a constructor strongly couples the class to shared_ptr, to the extent that it can only be owned by a shared_ptr - can't even be allocated on the stack.
Doesn't calling it from any member function do this?
Quote:Original post by Sneftel
Quote:Original post by Nitage
Calling it from a constructor strongly couples the class to shared_ptr, to the extent that it can only be owned by a shared_ptr - can't even be allocated on the stack.
Doesn't calling it from any member function do this?



To a certain extend, yes - but the coupling is less strong than if called in the constructors. There's nothing to stop a class from using enable_shared_from_this just so others can use shared_from_this on the precondition that is is being held by a shared_ptr elsewhere. That would render a subset of functions useable only when the object's being held by a shared_ptr, rather than the entire class (if it's used in all constructors).

Due to that coupling, I'm not an especially big fan of that functionality at all - if someone passed a reference to an instance, they probably didn't want you to start messing with that instance's lifetime - and if they wanted you to be able to take a strong reference to it, why didn't they pass you a shared_ptr?
Quote:Original post by Nitage
I think the problem here is that you take criticism personally. You've been advocating an overly prescriptive use of RAII which focuses too narrowly on shared_ptr. You've repeatedly refused to engage with the criticism, instead preferring to pretend that the use of smart pointers and RAII in general is being criticised.


I think your goal here is to "win" a nerdy argument, rather than to engage with the concepts I'm addressing, and as I don't wish to just reiterate the same concepts with words in a slightly different order, I don't think I'll continue to defend them. At the end of the day, I'm happy enough to let bug-tracker do the talking.

Quote:Original post by Nitage
Quote:Wait, what? Why would it be inappropriate to want to use shared_from_this in the constructor?


Calling it from a constructor strongly couples the class to shared_ptr, to the extent that it can only be owned by a shared_ptr - can't even be allocated on the stack. It also creates some lifetime issues that can be a bit tricky to manage (and yes Burnhard, shared_from_this can safely be used from a constructor).


No, it can't. This() can't be used in a constructor, because This() does not exist until after the constructor has done its work.

This topic is closed to new replies.

Advertisement