Link to home
Start Free TrialLog in
Avatar of LazyStudent
LazyStudent

asked on

Exception in C-tor

I would like to know what is the  best practice for the following case.

I have object Person and say 2 objects derived from Person - Man and Woman.
Person has property string sId, string sName
I would like to check to add derived objects with id validation. Let's say Man have id with 3 digits only and Woman - with 10 digits only.

I decided to create in Person virtual function setId and in Person constructor to call for this function and if it returns false - I throw an exception.
I overloaded this function in Man and Woman in order to validate properly id.

Some friend of mine said - that exception in C-tor - bad practice and it is better to create virtual function "Init" for Person ,overload it in Man and Woman and creation of objects will be like:
...
Man oMan;
if ( !oMan.Init("Jack", "232") )
{
// error routine
}
....

Who is right?

ASKER CERTIFIED SOLUTION
Avatar of EarthQuaker
EarthQuaker

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 ambience
>> Some friend of mine said - that exception in C-tor - bad practice and it is better to create virtual function "Init" for Person ,overload it in Man and Woman and creation of objects will be like:

Well your friend was partly wrong and partly right.

Its not a bad practice to throw exceptions in the constructor, in fact the only way to report an error is exceptions, as mentioned by EarthQuaker.

Though you have to be extra cautious with throwing exceptions because you might leak resources in some cases.

But the bigger problem with your approach is

>> I decided to create in Person virtual function setId and in Person constructor to call for this function and if it returns false - I throw an exception.

this will never ever work, ** virtual functions DONOT work from inside ctors **. It doesnt even make sense at all, because technically the object hasnt been constructed as yet. You can try that if you want to but you'll end up calling the base version of the virtual function and not the derived object.

Think about it the base is constructed first and the derivation later, you are in the process of constructing the base so a virtual call is impossible.

In that case your resort is the way your friend mentioned.

Man oMan;
if ( !oMan.Init("Jack", "232") )
{
// error routine
}

or you shift the checking portion to Man::Man().
Avatar of LazyStudent
LazyStudent

ASKER

ambience - thank you for clear my errorenous logic ( about using virtual function ).
I faced it already and was going to ask about it - how can I do workaround for it - but you already asked:-)
Collect points here:
https://www.experts-exchange.com/questions/20523574/Points-for-ambience.html
You are right.

It used to be that exceptions in constructors was a bad idea. The reason was that the run time left the object in a partially constructed manner and the destructor wasn't properly called and all kind of bad things happened.

However, all those things has been cleared up and throwing an exception in constructor is not only possible but is a good thing to do.

If nothing else it is good because a constructor cannot return a value so you can't indicate failure by returning a value. Throwing excpetion is therefore not only the preferred way but the only way. Well, you could also have a status member 'ctor_ok' and set to false if the object isn't good but that is troublesome, you would then have to test that member after.

Having an init function is definitely a bad design. It is usually good to have an init function and allow someone to construct the object using default constructor and then call init to fill it with values, but it shouldn't be the only way to fill it with values.

obj o("foo");

is more elegant and shorter and in all ways better than:


obj o;

o.init("foo");

However, it is good to allow for an init funciton. For example if creating an array of obj the default constructor is called and that constructor probably do not have available values to initialize the object properly and so:

obj * o = new obj[3];
o[0].init("foo");
o[1].init("bar");
o[2].init("baz");

becomes the way to do it for arrays.

What you should absolutely not require is something silly like this:

obj o("foo");

o.assert_ok(); // throw exception if o is not good.

What if people forget to call that function before they start to use the object? You might want to have a function assert_ok() but you should call it yourself in the constructor when you have valid data and you should also call it yourself in the init() function if you had such a function as well. Don't make an interface where people have to call function A and then call B and C etc...in specific sequence for no other reason than that one function verifies the object after a previous method call.

There are interfaces where you call functions in specific sequence but that is because it isn't given that you always want to do them all, one of more of them are optional and is used to enable/disable specific options. Required functionality should be placed in one specific function or at most two and when you have two it is because you allow for optional functions to be called in between:

obj.setup(....); // or in constructor...
obj.optionA(...);
obj.optionB(...);
// obj.optionC(...); // don't want C
obj.optionD(...);
obj.optionD(...); // option D can be repeated...
obj.do_the_job(....);

This interface has two required functions: setup() which isn't really required since the constructor calls it implicitely and the function do_the_job(...) which actually does the meat of the job. However, between setup and before you call the funciton to do the job you can optionally call several methods that sets up parameters for do_the_job. One reason why you want separate functions for that as opposed to extra arguments to do_the_job is perhaps to keep the arguments to do_the_job simple and yet able to handle a myriad of different parameters. Another reason is that perhaps one of the options can be set several times and they are each significant. If so you can always have an array argument for the options to do_the_job but that is troublesome for the caller to set up, he would then have to set up a struct array or something and fill it with values before calling the function.

An alternative here is to have the object having methods to set the options. These methods typically check the parameter values and then store them in an internal data structure hidden inside obj. do_the_job will then use that data structure to perform the actual job the function is supposed to do.

In any case, "no exceptions in constructor" was a valid thing to say several years ago in the early days of C++'s exception handling. It is not valid for modern standard ANSI C++ compilers.

Alf