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 Nitage
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 sounds like a pretty bizarre use case. As you said, any situations where the user code has a raw pointer to the object yet knows that a shared_ptr exists, the user code could just as easily be given a shared_ptr to begin with. It's also clearly not the intended purpose of shared_from_this -- see the documentation for details, not to mention the very name of the utility. Apparently, the Boost developers felt that it was natural for a class to rely on its own ownership semantics.

I think this might be a core point of contention between you and Burnhard. He cares about ownership flexibility at the class granularity; you care about it at the instance granularity too. I think for the majority of the nice, meaty classes one tends to hide behind shared_ptr-returning factory functions -- as opposed to Vec3 or ParsedURL -- I'm with him on that. It's natural for how a class' lifetime behaves to be considered part of its interface, and of course a class can rely on its own interface. And particularly in the realm of IOC, signal/slots, and other modern programming patterns, keeping the class from relying on it sharply limits your flexibility.
Advertisement
Quote:Original post by Burnhard
This() can't be used in a constructor, because This() does not exist until after the constructor has done its work.
Newer versions of shared_ptr get around this by lazily constructing the refcount block and filling it in later. Which creates its own problems, but at least solves that particular annoying problem.
Quote:Original post by Sneftel
Quote:Original post by Burnhard
This() can't be used in a constructor, because This() does not exist until after the constructor has done its work.
Newer versions of shared_ptr get around this by lazily constructing the refcount block and filling it in later. Which creates its own problems, but at least solves that particular annoying problem.


The version I'm using at the moment gives a bad_weak_ptr exception.
Quote:Original post by Burnhard
But the important point is that two-stage construction is always safer, because you can guarantee your constructor doesn't throw.

Why does that matter, especially as long as you're using RAII to ensure exception safety? As Nitage said, you're only moving the problem to a different place. Aardvajk has shown why the container argument doesn't hold, and anyway you can probably just use a container of shared_ptrs instead. I'm not seeing the need here, except for a very few cases.
Quote:
Quote:Original post by theOcelot
Does anyone remember what RAII stands for? It's "Resource Acquisition Is Initialization."
Yes. It's about the *destructors* of objects on the stack being executed when it unwinds.

What part of "resource acquisition" says "destructors" to you?
Quote:
Quote:Why not use them, even if you don't strictly "need" them? Who really needs RAII at all, when you can use free functions and gotos?

Absurd. Don't start misrepresenting what I'm saying.

Don't start misrepresenting what I'm saying: it was just an example, for crying out loud! Neither I nor, as far as I can tell, anyone else are arguing against shared_ptr or RAII (in general). I was just trying to make a point about the term "needing".

That point being, we do lots of things not because we "need" them, but because they make our life easier. We don't "need" shared_ptr's, since we could always just do ref-counting manually or use other memory management techniques. In some cases, we could even just let the OS clean up our mess, and that would at least work. But that's gross, and manual memory management is a pain, so we both use shared_ptr's extensively.

Similarly, virtual destructors (at least can) make life easier, have negligible cost, and the lack of them is gross. Why not?

Coming back to the original point of the thread, the point most of us are trying to make is that you don't "need" Init methods (except in a few exceptional cases), and usually they don't even make life easier.
Quote:
Quote:
Your bringing them up now is... telling. Speaking of straw men...

Yes, such as your comments on the use of raw functions and goto.

I was intentionally creating an absurd example to make a point, not to say that you were that stupid, but just to (hopefully) non-combatively point out an inconsistency in your reasoning by taking it to its logical extreme. It's very hard to debate with you taking critiques personally.

Quote:Original post by theOcelot
Quote:
Quote:Original post by theOcelot
Does anyone remember what RAII stands for? It's "Resource Acquisition Is Initialization."
Yes. It's about the *destructors* of objects on the stack being executed when it unwinds.

What part of "resource acquisition" says "destructors" to you?
The fact that that's the more important part. See here for concurring details.

Quote:virtual destructors (at least can) make life easier, have negligible cost, and the lack of them is gross. Why not?
A side effect of the factory-function-returning-shared_ptr pattern is that they're not required. It's like not giving your classes COM GUIDs: They can make life easier and have negligible cost. And if you're making a COM interface then the lack of them is gross. But if you're not, it isn't, really.
Quote:Original post by theOcelotAardvajk has shown why the container argument doesn't hold


I will reply to the container point because one of the arguments against what I'm doing is that it's somehow "restrictive". The container point was meant to demonstrate that it's often hard to avoid zombie objects because you often need a default constructor on objects used in containers. That is to say you can't use ".resize" on your container without the default constructor. If you refuse to implement another way to initialise your object, then you're not complying with the requirements of the container. That is itself a restriction. The point was not to say, "there's no other way of adding an item to a container" as there are clearly other approaches one could take (including push_back - provided you enjoy public copy constructors), or having a container of pointers. If you're going to do the latter, surely you should prefer a container of shared_ptr?

What I'm trying to say here is that because I'm always using shared_ptr, this is not really something I need to think about. So I'm not defending the specific case of using a container, but the principle of always preferring shared_ptr.

Quote:Original post by BurnhardThe point was not to say, "there's no other way of adding an item to a container" as there are clearly other approaches one could take (including push_back - provided you enjoy public copy constructors), or having a container of pointers. If you're going to do the latter, surely you should prefer a container of shared_ptr?

Right. If stuff isn't default-constructible, I just don't provide a default constructor and store it by (smart) pointer; it's probably a non-trivial class anyway that would benefit from a shared_ptr. So I still don't need an Init.

Quote:Original post by Sneftel
Quote:What part of "resource acquisition" says "destructors" to you?
The fact that that's the more important part. See here for concurring details.

Weird. I guess that makes sense when you think about it in the context of other resource-allocating methods. Mea culpa.

My error aside, my main point was that a constructor is the natural way to initialize an object.
Quote:
Quote:virtual destructors (at least can) make life easier, have negligible cost, and the lack of them is gross. Why not?
A side effect of the factory-function-returning-shared_ptr pattern is that they're not required. It's like not giving your classes COM GUIDs: They can make life easier and have negligible cost. And if you're making a COM interface then the lack of them is gross. But if you're not, it isn't, really.

I'm not familiar with COM, but I suspect that bringing all the COM machinery into your project is not exactly a negligible cost. I suppose if you were already working with it, it would be less significant, but still not the 17+n characters of a virtual destructor.

A virtual destructor is a core language feature, and I was always taught that it's just a Good Thing to do whenever virtual functions are involved. I suppose it may be more of a personal preference than I thought, but it doesn't make sense to me to sacrifice even that little bit of flexibility for the sake of a few keystrokes. You might at some point want to use another library's smart pointer, which might not be so smart, or something else weird. I don't want to change my memory management and in the process of tracking down a bug, discover its because I forgot to add a virtual destructor months ago when I assumed I wouldn't need it.

I personally am on the side of classes being oblivious to their storage whenever possible. A lot of times I just make them automatic variables and pass them around by reference, which still keeps their lifetime under control.
Quote:Original post by theOcelot
My error aside, my main point was that a constructor is the natural way to initialize an object.
I agree with that. The removal of the limitations on shared_from_this from a constructor (which, uh, I think is the case) mean that there's even less reason to have an init() function, even if there's also no huge reason not to.
Quote:
Quote:
Quote:virtual destructors (at least can) make life easier, have negligible cost, and the lack of them is gross. Why not?
A side effect of the factory-function-returning-shared_ptr pattern is that they're not required. It's like not giving your classes COM GUIDs: They can make life easier and have negligible cost. And if you're making a COM interface then the lack of them is gross. But if you're not, it isn't, really.

I'm not familiar with COM, but I suspect that bringing all the COM machinery into your project is not exactly a negligible cost. I suppose if you were already working with it, it would be less significant, but still not the 17+n characters of a virtual destructor.

I'm not talking about the full machinery of COM. Just the GUID, a unique integral identifier that's different from all the other GUIDs ever generated. It's just a one-liner, it takes up no space in objects, it has no significant downsides. Putting a GUID in the class is one of several things you need to do if you decide later on that you want it to be a COM interface. Likewise, putting on a virtual destructor is one of several things you need to do if you decide later on that you want the class to be creatable other than through the shared_ptr-returning factory. The only difference is that we C++ programmers were raised from wee lads to always put virtual destructors in our polymorphic classes, and we weren't raised to always put GUIDs on our classes. So when the odd situation crops up where the virtual destructor looks required but actually isn't, it looks gross to us. But it's not, really. It's just a subtle little side effect of how things are working in this situation.
About the default constructible thingy: C++0x will mop this argument away using perfect forwarding, move semantics and variadic templates:

#include <vector>int main(int argc, char *argv[]){    // Yes, local types now derive linkage from enclosing function    // so we can use them as template arguments    struct Foo {        float f;        int   i;        bool  b;        Foo (float f, int i, bool b) : f(f), i(i), b(b) {}    private:        Foo () = delete; // See, no default ctor().    };    std::vector<Foo> foos{{2.718, 0xBEEF, false}}; // Upon construction of vector    foos.emplace_back(3.141, 42, true); // Construct directly within vector.    return 0;}


g++ will kindly accept this code if you want to play with it.
Quote:Original post by phresnel
About the default constructible thingy: C++0x will mop this argument away using perfect forwarding, move semantics and variadic templates:

*** Source Snippet Removed ***

g++ will kindly accept this code if you want to play with it.


That's all good. I don't think we'll get 2010 until the next IT budget comes around.

This topic is closed to new replies.

Advertisement