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 Sneftel
Er, you should probably read the rest of the thread. shared_ptr's solution to the problem of incomplete types has the side effect of removing the need for virtual destructors, as long as the initial shared_ptr is constructed correctly.



Yes, and that's all good and well if you happen to be using shared_ptr, but try using intrusive_ptr without virtual d'tors and watch your application leak stuff all over the place.

Below is SiS-Shadowman's example modified for use with boost::intrusive_ptr ...


#include <iostream>#include <boost/intrusive_ptr.hpp>struct Data{	~Data() { std::cout << "I got destroyed" << std::endl; }};struct A{	void ref() { ++count; }	void unref()	{		--count;		if ( count == 0 )		{			delete this;		}	}	size_t count;	A() : count( 0 ) {}};void intrusive_ptr_add_ref( A* a ){	a->ref();}void intrusive_ptr_release( A* a ){	a->unref();}struct B : public A{    Data d;};int main( int argc, const char* argv[] ){	boost::intrusive_ptr< A > ptr( new B );}


Try it out yourself.

P.S. I realize that there are few problems that really require use of intrusive_ptr, but in general I think it's better to be safe than sorry. I mean if your class has any virtual functions, it'll have a vtable anyway so you may just as well go the extra mile and give it a virtual d'tor, too.
Advertisement
Quote:Original post by Red Ant
Yes, and that's all good and well if you happen to be using shared_ptr, but try using intrusive_ptr without virtual d'tors and watch your application leak stuff all over the place.
Which nobody was suggesting or even discussing, in particular Burnhard (whose approach doesn't even make sense with intrusive_ptr).
Quote:in general I think it's better to be safe than sorry.
This approach already allows you to be safe rather than sorry: By making the constructor private and only giving access to the factory function, you guarantee that all instances are shared_ptr managed. (This safety is also essential if you plan to do any shared_from_this.)
"if you are willing to sacrifice performance and flexibility to such a large degree in a bid to remove leaks, why are you using C++ instead of a garbage collected language?"

In what respect have I sacrificed flexibility? I haven't sacrificed any. In fact I've increased flexibility because I'm able to forget about micro-managing allocation. All I've done here is been rigorous in my application of some fundamental principles. The performance difference is minimal. If your program is slow because you're using smart pointers, then it's almost certainly the implementation detail that's at fault, not the use of the smart pointer.

"It means that member functions cannot be no-throw - which is a bit of a problem if you want your code to provide the strong exception guarantee."

In what respect does checking a parameter and throwing if it's invalid violate the strong exception guarantee? The guarantee is that either an exception is thrown and no state has changed, or the operation completed successfully.
"I'm curious - are you using this in a team setting, or are you doing solo development?"

It's a team setting. Our last project, where we put these principles into practice for the first time, came in on time (first time ever at my company!) and so far we have no nasty software related crashes/bugs in our bug-tracker (a few GUI issues which are concerned with design and a few hardware FPGA related issues have cropped up). It was a great success. Although I don't think it was a success due to the code in itself (we had a very solid design to start with), I seemed to spend less time on the project fiddling about with resource/allocation micro-managing related problems and more time implementing actual functionality.

The only issues I had while I was learning all about this stuff involved shared_ptr circular references (with parent/child relationships), which are easily broken by use of weak_ptr in the right place, and I also had trouble trying to work out why I couldn't use This() in a constructor (hence moving over to use ->Initialise(...) instead).
Quote:Original post by LionMX
I personally would do it like this:

//Your constructor
ClassOne::ClassOne()
{
//Stuff
if(error)
{
throw ErrorCode(DATA_PROBLEM)
}
}

//Global
ClassOne *p_myClassOne = NULL;
ClassTwo *p_myClassTwo = NULL;

int main(void)
{
try
{
*p_myClassOne = new ClassOne;
*p_myClassTwo = new ClassTwo;
}
}

Sorry about the mess above but no time to format it whilst im in work. Basically putting a throw in your constructor will give an error if an object fails to initialize (picked up by the try).

Keep in mind that before throwing you'll need to release any resources you successfully acquired in "//stuff".

I'd move error prone code out of the constructor whenever possible. Not facing this dilemma is definitely what I'd prefer.
Quote:Original post by Slavik81
Keep in mind that before throwing you'll need to release any resources you successfully acquired in "//stuff".


This is only true if those resources are not managed by some sort of smart_ptr. If they are and you throw an exception within the constructor of ClassOne, all fully constructed members (and base classes?) will be destroyed.

I hope I'm not mistaken on that fact too.
Quote:Original post by Burnhard
"It means that member functions cannot be no-throw - which is a bit of a problem if you want your code to provide the strong exception guarantee."

In what respect does checking a parameter and throwing if it's invalid violate the strong exception guarantee? The guarantee is that either an exception is thrown and no state has changed, or the operation completed successfully.


No-throw != Strong guarantee.

Basically, no-throw translates to does-not-throw, meaning that a function, ..., does not throw. The strong guarantee is about rollback semantics, the no-throw one is about never failing in no case.

For example, when you throw a ball into the outer space, than that has the strong guarantee. If it fails, the ball will come back to you == rollback.

An example for no-throw if you cancel out events with a probability below some certain threshold: You will die. This is guaranteed to succeed. You can't not die. I am sorry. The universe will die, too, as will our sun and every other sun. The ant crouching your hairs will die, too, btw. All of them will die, they can't not die, they won't survive. It has the no-throw guarantee. It will end. He comes.

You could also go back and dissect your rollback functionality: The rollback in itself must have the no-throw guarantee. If it only has the strong guarantee, the rollback will roll back, meaning that the upper rollback no longer succeeds, meaning that the upper function does no longer have a guaranteed rollback, i.e. no longer has the strong guarantee.



On another note: I think that if you do not write a virtual destructor, you should either seal the class, so you can't derive from it, or otherwise ensure that no one ever owns a pointer to base-class (e.g. by using factory methods and/or proper conversion overloads).
"If it only has the strong guarantee, the rollback will roll back, meaning that the upper rollback no longer succeeds"

I'm confused. Surely the upper rollback will only fail if it doesn't have the strong guarantee but the current context's does?
Quote:Original post by Burnhard
"If it only has the strong guarantee, the rollback will roll back, meaning that the upper rollback no longer succeeds"

I'm confused. Surely the upper rollback will only fail if it doesn't have the strong guarantee but the current context's does?


void foo () {    StateTweaker tweaker(*this);    try {         tweaker.palimPalum();    } catch (...) {         tweaker.rollback(); // <-- If this fails, *this                             //     might be screwed up, and                              //     foo() no longer has rollback()-semantics    }}


StateTweaker::rollback() must have the no-throw guarantee and must succeed in each and every case, no matter what. It. Must. Not. Fail.

Otherwise, if rollback() fails, whatver exception guarantee it provides, StateTweaker might leave behind a screwed up *this, one that is rolled back only half, or that contains invalid pointers.

And thus, *this is neither in advanced nor in rollbacked state, hence function foo() neither is no-throw nor strong.
Quote:I think you far overestimate the limitations in flexibility that the use of smart pointers imposes (essentially none -- just because you don't use manual lifetime management doesn't mean you can't), and far underestimate the performance difference between refcount-based memory management and an actual GC

I'm not criticising the use of smart pointers in general - I'm criticising this paticular (mis)use.

Quote:In what respect have I sacrificed flexibility? I haven't sacrificed any. In fact I've increased flexibility because I'm able to forget about micro-managing allocation.


Polymorphic base classes without virtual destructors cannot be used by anything that doesn't use the type erasure template magic that boost::shared_ptr does. This makes manual lifetime management more difficult - for example, these objects can't be safely stored in any of the boost pointer containers, an auto_ptr, scoped_pointer or unique pointer.

As for performance concerns - try benchmarking a boost::ptr_vector against a std::vector<shared_ptr>.

Quote:I'm confused. Surely the upper rollback will only fail if it doesn't have the strong guarantee but the current context's does? In what respect does checking a parameter and throwing if it's invalid violate the strong exception guarantee? The guarantee is that either an exception is thrown and no state has changed, or the operation completed successfully.

The strong guarantee is not transitive.

[Edited by - Nitage on July 2, 2010 8:38:06 AM]

This topic is closed to new replies.

Advertisement