Link to home
Start Free TrialLog in
Avatar of DanRollins
DanRollinsFlag for United States of America

asked on

class initializers

Submitted for discussion:

class MyObj {
     MyObj( int nMax )  :  m_nMax( nMax)    // ctor
     {  
     }
     int m_nMax;   // member
}

... initializes o.m_nMax when constructing the object.  How is that any better than:

class MyObj {
     MyObj( int nMax )    // ctor
     {
         m_nMax= nMax;
     }
     int m_nMax;   // member
}

When I was learning C++, I recall being confused by the former syntax (it looks like a base-class identifier), and I've often wondered why it would be used when the latter version (explicit assignment within the constructor body) is so much clearer.   My theory is that there is (or perhaps was) some possible CPU optimization possible by using that syntax.  But if that's the case, I'd assert that modern optimization techniques make it moot.

Q:  Do you use the former sytnax in your own code?
Q:  Is there some actual advantage to using it?
SOLUTION
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> how do you ensure all resources are reclaimed?

Urm, RAII idiom and/or constructor try blocks, maybe?
>> Urm, RAII idiom and/or constructor try blocks, maybe?

Don't take my words for it heres what Herb Sutter has to say http://www.gotw.ca/gotw/066.htm

the only (repeat only) possible use for a constructor function-try-block is to translate an exception thrown from a base or member subobject. That's Moral #1
from the C++ standard, clause 15.3, paragraph 10:
    Referring to any non-static member or base class of an object in the handler for a function-try-block of a constructor or destructor for that object results in undefined behavior.
You seemed to have missed two important points:

1. RAII idiom and/or constructor try blocks

The implication being that you would use either or both of these to take whatever action is necessary to finalise the failed construction of a class and together they make the use of assignment in the constructor body completely redundant.

2. Moral #3: Always perform unmanaged resource acquisition in the constructor body, never in initializer lists. In other words, either use "resource acquisition is initialization" (thereby avoiding unmanaged resources entirely) or else perform the resource acquisition in the constructor body.

In other words, if you use the RAII pattern there should never be a need to do anything else; the need for performing any assignment of members in the constructor is negated by the use of the RAII pattern. That said, it may be necessary to perform specific actions should the construction fail and that is where the try block comes in. For example, if your destructor would normally deallocate a resource being passed into the constructor of this object you can ensure the resource is deallocated using the try block).

from the C++ standard, clause 15.3, paragraph 10:
   12 The scope and lifetime of the parameters of a function or constructor extend into the handlers of a function-try-block.

NB. If you are coding C++ and not using the RAII pattern you are walking towards a world of pain/ sooner or later it will punch you in the face.
I must admit I don't know what the RAII pattern is, so I've asked a question about it here:
https://www.experts-exchange.com/questions/27684946/RAII-pattern.html
BTW: I'm not disagreeing with you per se, I'm just pointing out that whilst that is one way to resolve the issue you raise it is by no means necessary to go that route nor is that the best way to solve this issue as in itself it raises other problems.

For example, let's consider the situation where you are allocating heap memory...

foo()
{
   try
   {
      pi1_ = new int;
      pi2_ = new int;
      pi3_ = new int;
   }
   catch
   {
      // how do I know which of those pointers needs deleting?
   }
}

Open in new window


You could solve it like this...

foo()
{
   try
   {
      pi1_ = 0;
      pi2_ = 0;
      pi3_ = 0;
      pi1_ = new int;
      pi2_ = new int;
      pi3_ = new int;
   }
   catch
   {
      delete pi1_;
      delete pi2_;
      delete pi3_;
   }
}

Open in new window


Not really any better. I now, how about we do this?

foo()
   : raii_pi1(new int)
   , raii_pi2(new int)
   , raii_pi3(new int)
{
}

Open in new window


See. the point I am making is that constructor body assignment is just not necessary if you are using the RAII pattern. Further, if you do need to perform any post constructor-fail activity you can use the constructor try block for that.
or else perform the resource acquisition in the constructor body.

Use RAII and do what? Use std::string for char* and auto_ptr for pointers? And what about the insides of string? auto_ptr?

Keep using RAII and you will eventually have to confront resources in their most naked form at some level so got any solution for that? What are your recommendations considering the Buffer class I posted earlier?

To summarize: If theres an RAII solution then yes use it by all means - but when you are forced to write an RAII pay attention to exception safety.

>> The scope and lifetime of the parameters of a function or constructor extend into the handlers of a function-try-block.

What does this has to do with non-static members of the object? And with move semantics it is more toxic than ever to use any parameters because there is no way to know which one got its resources stolen from it. It would be interesting to see how the newer standards address this situation. Using an object that has been "moved out" is undefined behavior.

But that's irrelevant anyway.
I didnt notice followup comments before I posted my last comment

>> Further, if you do need to perform any post constructor-fail activity you can use the constructor try block for that.

The question is what kind of fail activity is OK to perform? The Moral is only exception translation.

Also coming back to the original context:

No Throw guarantee can not be provided with fucntion try block because there is no way to absorb the exception. If you wont throw the source exception gets re-thrown automatically.

In that case even with RAII available - in certain cases - the only way to provide no-throw guarantee would be to use body initialization.
>> Use RAII and do what? Use std::string for char*
Well yes, that's kinda the point of that class isn't it?

>> and auto_ptr for pointers?
Only if you like playing with fire - it's a pretty evil little class that is badly misunderstood by most programmers. Use a proper reference counted smart pointer. It's pretty easy to code one or just use the ones with boost. You seem to be using C++11, that standard comes with them as part of it.

>> And what about the insides of string? auto_ptr?
What about them? I don't see the point you're making.

>> Keep using RAII and you will eventually have to confront resources in their most naked form at some level so got any solution for that?
Again, I don't see the point you are making.

>> What are your recommendations considering the Buffer class I posted earlier?
Urm, use a vector - why reinvent the wheel? If you're bent on implementing your own, how about using shared_ptr (boost or c++11) or its array equivalent?

>>  but when you are forced to write an RAII pay attention to exception safety.
But, the point is if you *are* force to write one you're still only going to be playing with intrinsics deep down and there are already RAII solutions for those so I still don't see your point!

>> What does this has to do with non-static members of the object?
Nothing, you brought that up not me.

>> move semantics it is more toxic than ever to use any parameters because there is no way to know which one got its resources stolen from it
Well, this is a bit of a strawman argument. You only use move semantics when you need them and when using move semantics the actual implementation is such that your construction isn't going to be doing any direct initialisation; that's already been done and being moved into the construction.

>> But that's irrelevant anyway.
Yes, it is :)

>> The question is what kind of fail activity is OK to perform?
Did I not already give an example? Was my explaination not clear enough?

>> No Throw guarantee can not be provided with fucntion try block because there is no way to absorb the exception
It doesn't matter. You should not offer a no-throw guarentee if the class members can't also honour that guarentee. If an entity throws on construction it is *not* constructed so there is no sane case to still allow your class to construct. Again, this is a straw man argument.

>> the only way to provide no-throw guarantee would be to use body initialization.
You give me an example where you think this is true and I'll see if I can show you why it's not :)
>> and I'll see if I can show you why it's not :)
I probably should have said, "and I'll see if I can show you why that's mis-guided". Heh.
"It's pretty easy to code one"

That was the point!

>> But, the point is if you *are* force to write one you're still only going to be playing with intrinsics deep down and there are already RAII solutions for those so I still don't see your point!

Im not taking this as a practical lessons thread but an academic excercise therefore "why reinvent the wheel" is not impressive enough to dismiss my claims. How about you provide a safe implementation of the following - without any body code? If you use any RAII then also provide their body-less implementations (to prove you point ofcourse).

string::string(const string& other)
{
}


>> What does this has to do with non-static members of the object?
>> Nothing, you brought that up not me.

I didnt - my reference was specific to undefined behavior of using non-static members and the reason I mentioned that was because I thought you referred to the fact that if an exception gets thrown then you can check members and reclaim all resources in the handler for try block. like if(this->somemember != null) free(this->somemember) ---- and my point was that such code isnt desirable.

>> move semantics it is more toxic than ever to use any parameters because there is no way to know which one got its resources stolen from it
>> Well, this is a bit of a strawman argument. You only use move semantics when you need them and when using move semantics the actual implementation is such that your construction isn't going to be doing any direct initialisation; that's already been done and being moved into the construction.

I have no idea why anyone would believe that. Its irrelevant to the question under discussion therefore I'm going to keep quiet here.

>> The question is what kind of fail activity is OK to perform?
>> Did I not already give an example? Was my explaination not clear enough?

Which example? foo() Your examples of foo() support my claims not yours.

class Class
{
      Class(Class o)
      try : member1(o.member1), member2(o.member2)
      {
      }
      catch(...)
      {
        // Tell me its safe to access members here?
      }
}

>> No Throw guarantee can not be provided with fucntion try block because there is no way to absorb the exception
>> It doesn't matter. You should not offer a no-throw guarentee if the class members can't also honour that guarentee. If an entity throws on construction it is *not* constructed so there is no sane case to still allow your class to construct. Again, this is a straw man argument.

If mines a straw man yours are red herring. Where is this coming from: "If an entity throws on construction it is *not* constructed so there is no sane case to still allow your class to construct"? Personally I have problems imposing ideas on others including like you are insane if somethings coming to your mind. All this time I have been talking about the opposite case where you do not want the entity to throw and you accuse me of that?
 
Do the std::istream classes throw if the stream fails to open? Have you ever used stream.good()? There are good and valid cases where you want to ** absorb ** any possible construction time exceptions and have your object still live.

Ah another strawman you see?

How about writing an STL stream that wraps up a third party API that throws for all kind of failures but you want your STL stream to behave exactly like the rest out there and provide the same guarantees. Sane enough?

>> the only way to provide no-throw guarantee would be to use body initialization.
>> You give me an example where you think this is true and I'll see if I can show you why it's not :)

class Recordset
{
      // How can I provide no-throw for constructor?
      Recordset()
      try : someones_coolest_raii_for_unmanaged_recset( nukeMe() )
      {
            // they say Im misguided trying to fail silently :(
      }
      catch(...)
      {
        // This isnt going to stop
      }
      
      bool isValid() const { ... }
}
ambience,

Two things...

1. I'll come back to this later. I'm busy with other stuff right now so please don't take my lack of a response to mean anything other than that.

2. I'd prefer it if we could inject a little love into this exchange.

We are allowed to disagree and argue (I use that word in its purest sense) without taking it personally. I love a good argument as (a) it is sometimes an opportunity for me to educate and (b) it is often an opportunity for me to learn. Either way, it's never a wasted opportunity.

So, keep smiling and I'll get back to this later once RL work is no longer a burden to my EE time ;)
ambience,

Apologies for not being able to reply to you sooner but I have literally been up to my neck in it all day.

Firstly, I sense from the tone of your last comment you are feeling insensed. There really is no reason to be. We're all friends here and we're just having a debate on something we don't agree on. That's fine, we are allowed to disagree but still remain on good terms. Hell, if this wasn't the case my wife and I would never talk :)

So, please don't take anything in this discussion personally. You've been around long enough to know that none of us in the C++ zone every make things personal. We leave that sort of churlish behaviour to those weirdos in the SQL/Database zones! So, consider this the hand of friendship.

Now with that out of the way, let's see if we can clear this misunderstanding up...

>> How about you provide a safe implementation of the following - without any body code?
I'm not quite sure how you define safe. I'm going to assume you mean define a copy-constructor that doesn't throw. The point I keep making to you though is what would be the point of doing so? If you can't copy-construct your object then the only sensible thing to do is throw. This, I fear, is the cause of the misunderstanding.

I am asserting (with good reason) that trying to implement a constructor that doesn't throw if it or its base-class or members fail to construct is nearly always going to end in tears.. If the object hasn't successfully constructed how are you meant to do anything sensible with it? Ok, you could probably riddle your class with code to account for various members (or base) not constructing properly but the fact is this is really not a rebust way to implement an object. There is a good reason the constructor try block always re-throws, there is very little point doing anything else.

>> my reference was specific to undefined behavior of using non-static member
Right, but the point I am making is that at no time did I make a suggestion of doing that -- that is something you brought up not I.

>> I thought you referred to the fact that if an exception gets thrown then you can check members and reclaim all resources
No. I was making the assertion that a constructor try block in tandam with the RAII pattern covers all the bases you'd need. RAII for resource deallocation (not using RAII for members is a problem waiting to happen as I demonstrated a few posts ago and (if necessary) you have the opportunity to perform any other finalisation via the try block handler.

>> my point was that such code isnt desirable.
I agree, but then again I didn't actually say that did I? :)

>> Its irrelevant to the question under discussion therefore I'm going to keep quiet here.
Again, you brought up the subject of move semantics and I was just pointing out that it's actually a moot point.

>> Your examples of foo() support my claims not yours
I never actually made such claim. Re-read everything that I've posted... point me to a place where I made such a claim and I will make an immediate acknowledgement of mea culpa.

Class member/base isn't the only finalisation work that might be necessary if a constructor fails. There are other things you might wish to do and the try block handlers give you the opportunity to do so. For example, you might have a connection to a database that needs to be closed or maybe your class is performing static reference counting, or maybe your constructor locked a mutex and needs to unlock it (although why you'd not use the RAII pattern for these kind of things I'm not sure!).

So, back to constructors and nothrow guarantees...

If a class has not fully constructed trying to continue using it only ever going to end in tears. An to be clear So, my point was if you are going to offer a nothrow guarrantee you should (can!) only do so if the base class and all the member of your class and the base class (and the base of the base and so on) offer the same guarantee. If they don't then neither can you without compromising the integrity of your code security.

If they don't then you can't sensibly offer it either because (and I'll say it again) if you try and swallow the exception you'll end up with a class that is not sane (ie. you have no idea about the true state of the class, its members of any of the base classes/members). If your inheritance chain and members do offer that guarantee then your class can also offer it but in that case you have nothing to fear because nothing will actually throw. So, again, the point is actually a moot one.

The problem with swallowing a constructor exception is that there is no sane way to let the constructee know the construction failed, so either you have to riddle your code with logic to deal with that or you have to implement a post-construction test (whilst leaving the object in the so-called "zombie state") and rely on the constructee checking it. Even if you decide to go through all that how are you going to deal with base-class exception? You can't catch them!

I hope my position on this is now clear and we can all move on with life :)
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of phoffric
phoffric

>> For initializing const and reference members, I suppose that the initializer list is useful or even needed.
Also initializer list needed sometimes in derived class - see my previous post for example.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I'm pretty sure we've already covered that. Did you read all the comments before posting?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> Why should I care?
Yes. Not only is it less efficient (most noticeable with user defined type) but the assignment operator may very well have side-effects that you do not want to be exercised when you are only interested in construction semantics.

>> Won't both methods do that?
Yes, but not in the same way and the difference is important (or, at least, it is important you understand the difference).

>> the generated ASM code, the exact same series of opcodes is executed
For the construction/assignment of a simple intrinsic this is quite probably the case; not so in the case of user defined types.

>> In the former, if I want to debug the code, where do I put the breakpoint?
Well, in the case of an intrinsic it doesn't really matter. In the case of a user defined type; how about the constructor of that type?

>> In the former, the variables are set before the first (if any) statements in the ctor.
Precisely.

>> But the latter also gives me the option of setting them later.
but, you have that option anyway... don't use an initialisation list, use a constructor assignment. Horses for courses :)

Dan, I'm not trying to convince you to use assignment over initialisation lists. It makes no odds to me how to construct classes you write. I am only providing you with empirical answers. If you wish to call this an aardvark feel free to do so :-p
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of DanRollins

ASKER

Thanks to all who contributed.