Initializer list and instance variables

S

Simon Elliott

Can I use an instance variable as an argument in an initialiser list as
follows:

class bar
{
private:
int i1_;
public:
bar(int i1):i1_(i1){}
};

class fooBase
{
private:
bar& ref_my_bar_;
public:
fooBase(bar& ref_my_bar):ref_my_bar_(ref_my_bar){}
virtual ~fooBase(void){}
};

class fooDerived:public fooBase
{
private:
bar my_bar_;
public:
fooDerived(void):fooBase(my_bar_),my_bar_(42){} // ???
virtual ~fooDerived(void){}
};

This compiles OK, and intuitively I'd have thought it was safe whether
fooBase(my_bar_) or my_bar_(42) gets executed first. I'm simply passing
a reference down to a base class, and whether my_bar_'s constructor
gets called before or after this should make no odds.

Does the standard mandate any particular order of execution for this
combination?

Is what I'm doing safe in general, or are there times when this
approach won't do what I'd expect?
 
V

Victor Bazarov

Simon said:
Can I use an instance variable as an argument in an initialiser list as
follows:

class bar
{
private:
int i1_;
public:
bar(int i1):i1_(i1){}
};

class fooBase
{
private:
bar& ref_my_bar_;
public:
fooBase(bar& ref_my_bar):ref_my_bar_(ref_my_bar){}
virtual ~fooBase(void){}
};

class fooDerived:public fooBase
{
private:
bar my_bar_;
public:
fooDerived(void):fooBase(my_bar_),my_bar_(42){} // ???
virtual ~fooDerived(void){}
};

This compiles OK, and intuitively I'd have thought it was safe whether
fooBase(my_bar_) or my_bar_(42) gets executed first. I'm simply passing
a reference down to a base class, and whether my_bar_'s constructor
gets called before or after this should make no odds.

Does the standard mandate any particular order of execution for this
combination?

The order of initialising is (1) base classes in the order of their
declaration, then (2) the members in the order of their declaration
in the class.
Is what I'm doing safe in general, or are there times when this
approach won't do what I'd expect?

What you do is rather unsafe. The 'my_bar_' does not yet represent
a complete object of type 'bar' at the time of initialising of 'fooBase'.
While in your particular example 'fooBase' only stores the reference to
the object with which it is initialised, somebody can later come in and
change the behaviour of 'fooBase' to actually do something with the
object of type 'bar' in the constructor, thinking that the reference is
to a good and complete object. An attempt to call a member function for
an object that hasn't been constructed yet causes undefined behaviour,
IIRC.

Victor
 
S

Simon Elliott

The order of initialising is (1) base classes in the order of their
declaration, then (2) the members in the order of their declaration
in the class.

So in my example, the base class constructor gets called before my_bar_
is initialised.
What you do is rather unsafe. The 'my_bar_' does not yet represent
a complete object of type 'bar' at the time of initialising of
'fooBase'. While in your particular example 'fooBase' only stores
the reference to the object with which it is initialised, somebody
can later come in and change the behaviour of 'fooBase' to actually
do something with the object of type 'bar' in the constructor,
thinking that the reference is to a good and complete object. An
attempt to call a member function for an object that hasn't been
constructed yet causes undefined behaviour, IIRC.

So at the very least I need a comment in the constructor telling folk
not to mess with ref_my_bar_.

Is there a safer way of achieving this? I suppose I could do:

class fooBase
{
private:
bar* ptr_my_bar_;
public:
fooBase(void):ptr_my_bar_(0){}
virtual ~fooBase(void){}
void SetBar(ptr_my_bar){ptr_my_bar_=ptr_my_bar;}
};

class fooDerived:public fooBase
{
private:
bar my_bar_;
public:
fooDerived(void):fooBase(),my_bar_(42){SetBar(&my_bar_);}
virtual ~fooDerived(void){}
};

But in this case ptr_my_bar_ starts life as a null pointer. I suppose
that at least it's fairly obvious that this is the case. But a
developer coding a new fooDerived could forget to call
fooBase::SetBar(), ptr_my_bar_ would be null during all of fooBase
lifetime, and the error would not be caught at compile time.
 
V

Victor Bazarov

Simon said:
So in my example, the base class constructor gets called before my_bar_
is initialised.

Yes. The memory for the 'my_bar_' subobject has been allocated, but its
constructor has not been called. So, the object 'my_bar_' doesn't really
exist yet at that point.
So at the very least I need a comment in the constructor telling folk
not to mess with ref_my_bar_.

I say.
Is there a safer way of achieving this? I suppose I could do:

class fooBase
{
private:
bar* ptr_my_bar_;
public:
fooBase(void):ptr_my_bar_(0){}
virtual ~fooBase(void){}
void SetBar(ptr_my_bar){ptr_my_bar_=ptr_my_bar;}

You mean

void SetBar(bar*ptr_my_bar){ptr_my_bar_=ptr_my_bar;}
};

class fooDerived:public fooBase
{
private:
bar my_bar_;
public:
fooDerived(void):fooBase(),my_bar_(42){SetBar(&my_bar_);}
virtual ~fooDerived(void){}
};

But in this case ptr_my_bar_ starts life as a null pointer. I suppose
that at least it's fairly obvious that this is the case. But a
developer coding a new fooDerived could forget to call
fooBase::SetBar(), ptr_my_bar_ would be null during all of fooBase
lifetime, and the error would not be caught at compile time.

All is true. That's why making the base class depend on an object in
a derived class is not such a great design solution.

Victor
 
S

Simon Elliott

That's why making the base class depend on an object in
a derived class is not such a great design solution.

The problem here is:

1/ In the real design, fooDerived needs a class which inherits from a
bar. The base class needs to talk to the interface of barBase, but the
derived class needs to talk to the extra functionality implemented by
barDerived. There will be lots of fooDerived, each with their
corresponding barDerived. (ie there will be a fooDerivedA with a
barDerivedA, a fooDerivedB with a barDerivedB, etc)

2/ I might have handled this using inheritance (although even that
would be imperfect as a foo HAS a bar but a foo IS NOT a bar.) That
way, I wouldn't need to pass a pointer to barDerived down to fooBase.
But as it happens, fooDerived needs two instances of barDerived.

So I'm looking at some alternate designs, perhaps integrating the two
objects into a fooBarBase...
 
V

Victor Bazarov

Simon said:
[...]
So I'm looking at some alternate designs, perhaps integrating the two
objects into a fooBarBase...

Post to comp.object, they are really good with suggesting designs.

V
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top