[comp.std.c++] Constructor question

jamshid@ut-emx.uucp (Jamshid Afshar) (05/17/91)

In article <DSOUZA.91May3150307@gwen.cad.mcc.com> dsouza@gwen.cad.mcc.com (Desmond Dsouza) writes:
>All data members which are objects have to be initialized before
>the body of the constructor. There is this basic problem with 
>computing arguments for constructors in the initialization list:
>
>  You cannot compute any temporaries (e.g. common subexpressions) 
>  which are needed for initialization of such data members.

<A stripped-down version of his example follows.>

struct A {
   A(int m, int n);
};

struct B {
   A a1;
   B(int j)
      : a1( F1(j), F1(j)+1 ) {} // F1(j) is very time-cosuming
};

>// Here is what I would like to do, in a completely fictitous 
>// and probably highly ambiguous syntax:
>
>B::B(int j)
>:
>  { int f1 = F1(j);
>    a1(f1, f1+1);
>  }
>{
>  .. actual constructor body
>}
>
>Using globals, "memoizing" functions, etc. is not a solution to this.
>
>Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com

When I need to do general compuation during class member initialization
I use static member functions, but they can't help in your example.

I encountered a similar problem in which I had a Base class
constructor which took two parameters and a Derived class always
passed it duplicate items (ie. Base(foo(j), foo(j))) where foo(j) was
time-consuming.  What I did was add another constructor to Base which
only took one parameter.  This was a good move as it turned out I
needed to do this kind of construction just as often as the original
way.  Another solution if you do not want to mess with the Base class
is to create an intermediate class.

One solution to your example is to define class A above to be:

struct A {
  A(int m, int n);
  A(int m);  // construct an A whose n is one more than m
};

Yes, I know this is obvious and it's not always so simple, but I've
always found a solution like this to not only work, but actually end
up reflecting the problem better.

I like the current constructor format:
	Class(parms) : Base(params), member1(params), member2(params)
	   { ...body... }
because it makes it clear that you are constructing objects as opposed
to executing statements in the member initialization list.  When you
are inside curly-braces you shouldn't have to worry whether an object
has actually been constructed (which is one reason I believe goto's
were restricted in C++).  I think introducing two blocks for
constructors would lead to errors using objects before they are
constructed.  Actually, this is already a problem because at least my
compiler (Borland C++ 2.0) does not catch the use of an object before
it's constructor call in the member initalization list.  I don't
believe the ARM requires it to be flagged as an error, but it sure
would be a nice warning (also remember members are initalized in
declaration order, not their order in the member initalization list).
Speaking of special warnings for constructors, the use of virtual
functions in a constructor would also be nice.

Example taken from BC++ bug list at sun.soe.clarkson.edu in the file
~ftp/pub/Turbo-C++/bug-report.

	class String {
	  char* s;
	  unsigned Len;
	public:
	  String(const char* p);     // char* --> String
	  //...
	};
	String::String(const char* p) : Len(strlen(p)), s(new char[Len+1])
	{			/////// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	  strcpy(s, p);		/////// Missed feature: TC++ should warn here
	}			/////// Ex: `Len used before it is initialized'

Jamshid Afshar
jamshid@emx.utexas.edu

davidm@uunet.UU.NET (David S. Masterson) (05/18/91)

>>>>> On 3 May 91 20:03:07 GMT, dsouza@gwen.cad.mcc.com (Desmond Dsouza) said:

Desmond> In article <71952@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK)
Desmond> writes:

Jim> Again, if you want to factor out common code from constructors, I
Jim> recommend you follow Bjarne's suggestion: write a private init() routine
Jim> which is called from within each constructor.

Desmond> Sorry, but I dont think this works in general.

Desmond> All data members which are objects have to be initialized before
Desmond> the body of the constructor. There is this basic problem with 
Desmond> computing arguments for constructors in the initialization list:

Desmond> You cannot compute any temporaries (e.g. common subexpressions) which
Desmond> are needed for initialization of such data members.

Desmond> class A { public: A(int m, int n); };

Desmond> class B {
Desmond> public:
Desmond>   B (int j);
Desmond> private:
Desmond>   A a1;	// initialized with F1(j), F1(j)+1 for 
Desmond>                //some horribly complex F1
Desmond> };

Desmond> B::B(int j)
Desmond> // how can I share the computation of F1(j) here ???
Desmond> : a1 (F1(j), F1(j)+1)
Desmond> {
Desmond>   // this part does not help: I cannot modify a1 at this point.
Desmond> }

WARNING: Novice code coming...

Would a possible solution to this be:

B::B(int j) : a1()
{
	int	x = F1(j),
		y = x + 1;

	A	temp(x,y);

	a1 = temp;	// why can't you modify a1 using copy assignment?
}
--
====================================================================
David Masterson					Consilium, Inc.
(415) 691-6311					640 Clyde Ct.
uunet!cimshop!davidm				Mtn. View, CA  94043
====================================================================
"If someone thinks they know what I said, then I didn't say it!"

dsouza@gwen.cad.mcc.com (Desmond Dsouza) (05/21/91)

cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

   Desmond> You cannot compute any temporaries (e.g. common subexpressions) which
   Desmond> are needed for initialization of such data members.

   Desmond> class A { public: A(int m, int n); };

   Desmond> class B {
   Desmond> public:
   Desmond>   B (int j);
   Desmond> private:
   Desmond>   A a1;	// initialized with F1(j), F1(j)+1 for 
   Desmond>                //some horribly complex F1
   Desmond> };

   Desmond> B::B(int j)
   Desmond> // how can I share the computation of F1(j) here ???
   Desmond> : a1 (F1(j), F1(j)+1)
   Desmond> {
   Desmond>   // this part does not help: I cannot modify a1 at this point.
   Desmond> }

   WARNING: Novice code coming...

   Would a possible solution to this be:

   B::B(int j) : a1()
   {
	   int	x = F1(j),
		   y = x + 1;

	   A	temp(x,y);

	   a1 = temp;	// why can't you modify a1 using copy assignment?
   }
   --

You make assumptions about default constructor (0 arguments) and the 
assignment operator which are not valid in general.

Someone else suggested deriving a class from A and using that instead.
That is a pretty expensive way of computing a temporary integer!

-- Desmond.
--

-------------------------------------------------------------------------------
 Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com | Phone: [512] 338-3324
 Box 200195, Austin, TX 78720 | UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!dsouza

jimad@microsoft.UUCP (Jim ADCOCK) (05/21/91)

In article <DSOUZA.91May3150307@gwen.cad.mcc.com> dsouza@gwen.cad.mcc.com (Desmond Dsouza) writes:

>  You cannot compute any temporaries (e.g. common subexpressions) 
>  which are needed for initialization of such data members.

Consider:

----

int F1(int j) { /* something really really big and horrible here */ return j;}

class A {
  public: A(int m, int n);
};

class B {
public:
  B (int j);
private:
  int f1;
  A a1;	// initialized with F1(j), F1(j)+1 for some horribly complex F1
};

B::B(int j)
// how can I share the computation of F1(j) here ??? !!! Try the following:
: f1(F1(j)), a1(f1, f1+1)
{
  // this part does not help: I cannot modify a1 at this point.
}

dsouza@gwen.cad.mcc.com (Desmond Dsouza) (05/23/91)

In article <72461@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:

   >  You cannot compute any temporaries (e.g. common subexpressions) 
   >  which are needed for initialization of such data members.

   Consider:

   ----

   int F1(int j) { /* something really really big and horrible here */ return j;}

   class A {
     public: A(int m, int n);
   };

   class B {
   public:
     B (int j);
   private:
     int f1;
     A a1;	// initialized with F1(j), F1(j)+1 for some horribly complex F1
   };

   B::B(int j)
   // how can I share the computation of F1(j) here ??? !!! Try the following:
   : f1(F1(j)), a1(f1, f1+1)
   {
     // this part does not help: I cannot modify a1 at this point.
   }


No good. You cannot afford to add a data member per object instance to 
simply store an intermediate constructor variable.

(There is also the subtle point about order of initialization of data 
 members -- it follows the order in the class definition, so you had it right).

-- Desmond.
--

-------------------------------------------------------------------------------
 Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com | Phone: [512] 338-3324
 Box 200195, Austin, TX 78720 | UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!dsouza

euaeny@eua.ericsson.se (Erik Nyquist) (05/24/91)

dsouza@gwen.cad.mcc.com (Desmond Dsouza) writes:

>In article <72461@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:

>   >  You cannot compute any temporaries (e.g. common subexpressions) 
>   >  which are needed for initialization of such data members.

>   Consider:

>   ----

>   int F1(int j) { /* something really really big and horrible here */ return j;}

>   class A {
>     public: A(int m, int n);
>   };

>   class B {
>   public:
>     B (int j);
>   private:
>     int f1;
>     A a1;	// initialized with F1(j), F1(j)+1 for some horribly complex F1
>   };

>   B::B(int j)
>   // how can I share the computation of F1(j) here ??? !!! Try the following:
>   : f1(F1(j)), a1(f1, f1+1)
>   {
>     // this part does not help: I cannot modify a1 at this point.
>   }


>No good. You cannot afford to add a data member per object instance to 
>simply store an intermediate constructor variable.

>(There is also the subtle point about order of initialization of data 
> members -- it follows the order in the class definition, so you had it right).

Why no extend the language and make it possible to declare temporaries in the
initialization list? ;-) 

(yes, I know! We should try to standardize the language, not change it.)

class A {
  public: A(int m, int n);
};

class B {
public:
  B (int j);
private:
  A a1;	// initialized with F1(j), F1(j)+1 for some horribly complex F1
};

B::B(int j)
// how can I share the computation of F1(j) here ??? !!! Is this what you need?
:   int f1 = F1(j), a1(f1, f1+1)
{
  // this part does not help: I cannot modify a1 at this point.
}

I don't say that we should change the language, but how can we solve this 
problem without language extensions? 
We could also ask ourselves if we really should try to solve this problem.
That surely depends on how common it is! 

Erik Nyquist Ellemtel Utecklings AB  We are no longer the knights that say Ni!
             Box 1505                We are the knights that say:
             S-125 25 Alvsjo, Sweden Iky,iky,iky,iky,patang,zoop-boing, zowie.

--
Erik Nyquist Ellemtel Utecklings AB  We are no longer the knights that say Ni!
             Box 1505                We are the knights that say:
             S-125 25 Alvsjo, Sweden Iky,iky,iky,iky,patang,zoop-boing, zowie.

marc@mit.edu (Marc Horowitz) (05/28/91)

In article <1991May23.170530.23037@eua.ericsson.se> euaeny@eua.ericsson.se (Erik Nyquist) writes:

   dsouza@gwen.cad.mcc.com (Desmond Dsouza) writes:

   >In article <72461@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:

   >   >  You cannot compute any temporaries (e.g. common subexpressions) 
   >   >  which are needed for initialization of such data members.

   I don't say that we should change the language, but how can we solve this 
   problem without language extensions? 
   We could also ask ourselves if we really should try to solve this problem.
   That surely depends on how common it is! 

I posted to comp.lang.c++ last night with almost exactly the same
problem.  A local friend mentioned that this issue had been recently
discussed, so I'm responding to that original post.  I 

I'll start by saying that this problem should definitely be looked at.
It apparently happens at least somewhat often, since it happened to me
last night.  I had an even more difficult situation.  My classes look
like this:

class A {
  public:
    A(int a, int b, int c, int d);

// ...
};

class B : public A {
  public:
    B(char *s)
      :A(/* ??? */)
	{ /* too late */ }

// ...
};

void f(const char *s, int *a, int *b, int *c, int *d);

f takes a string, and parses the integers out of it.  It's somewhat
expensive.

Now, how should I go about doing this?  Every solution I've been able
to come up with is a morally abhorrent kludge involving some large
number of static variables.  I really would like to be able to have a
block where I could define a few local temporaries, call f, and pass
those temps into the base constructor.  I could add another
constructor to A, but A shouldn't need to depend on f, since f is
really a part of B.  Also note that subclassing won't help, since the
arguments to A aren't functions of a common temporary, as f(x) and
f(x)+1 are in the original example.

I agree with Desmond:  this seems like a definite flaw in the
language.

Someone mentioned that adding another block where temporaries could be
created could result in programmers accidentally thinking the object
was constructed while writing code in this block.  I think this
problem is solvable.  If the block is in a scope similar to that of a
static member function (e.g., ``this'' is not defined), then any use
of a non-static member is an error.  I think this is the way it should
be, and is not overly restrictive.  As long as someone can come up
with a non-ambiguous syntax, it doesn't seem that code generation
should be too difficult, either.

		Marc
--
Marc Horowitz <marc@mit.edu>					617-253-7788

pena@brainware.fi (Olli-Matti Penttinen) (05/30/91)

In article <MARC.91May27140938@steve-dallas.mit.edu> marc@mit.edu (Marc Horowitz) writes:

	[ lotsa stuff zapped ]

   I'll start by saying that this problem should definitely be looked at.
   It apparently happens at least somewhat often, since it happened to me
   last night.  I had an even more difficult situation.  My classes look
   like this:

   class A {
     public:
       A(int a, int b, int c, int d);

   // ...
   };

   class B : public A {
     public:
       B(char *s)
	 :A(/* ??? */)
	   { /* too late */ }

   // ...
   };

   void f(const char *s, int *a, int *b, int *c, int *d);

   f takes a string, and parses the integers out of it.  It's somewhat
   expensive.

   Now, how should I go about doing this?  Every solution I've been able
   to come up with is a morally abhorrent kludge involving some large
   number of static variables.  I really would like to be able to have a
   block where I could define a few local temporaries, call f, and pass
   those temps into the base constructor.  I could add another
   constructor to A, but A shouldn't need to depend on f, since f is
   really a part of B.  Also note that subclassing won't help, since the
   arguments to A aren't functions of a common temporary, as f(x) and
   f(x)+1 are in the original example.

Do you ever use f for anything but to initialize or change the value
of some A. If not, then f would really belong in A. In anycase, why
not add A::A(const char *); that uses f to compute the 4 ints.

Besides, Bjarne's proposal to use a protected init-method works well,
too, iff A has a default constructor. Admittedly, things might get a
little dirty if an A cannot have a meaningful state constructed out of
nothing. In that case, one could (accidentally) use the value of an A
before the object is fully constructed, so the problem would still be
there.

All in all, I don't see a reason to extend the language just because
a new feature would occasionally save a few keystrokes.

	[ more text deleted ]

		   Marc
   --
   Marc Horowitz <marc@mit.edu>					617-253-7788


==pena
--
Olli-Matti Penttinen <pena@brainware.fi> | "When in doubt, use brute force."
Brainware Oy                             |    --Ken Thompson
P.O.Box 330                              +----------------------------------
02151  ESPOO, Finland       Tel. +358 0 4354 2565       Fax. +358 0 461 617

niklas@appli.se (Niklas Hallqvist) (06/02/91)

pena@brainware.fi (Olli-Matti Penttinen) writes:

:In article <MARC.91May27140938@steve-dallas.mit.edu> marc@mit.edu (Marc Horowitz) writes:

:	[ lotsa stuff zapped ]

:   I'll start by saying that this problem should definitely be looked at.
:   It apparently happens at least somewhat often, since it happened to me
:   last night.  I had an even more difficult situation.  My classes look
:   like this:

:   class A {
:     public:
:       A(int a, int b, int c, int d);

:   // ...
:   };

:   class B : public A {
:     public:
:       B(char *s)
:	 :A(/* ??? */)
:	   { /* too late */ }

:   // ...
:   };

:   void f(const char *s, int *a, int *b, int *c, int *d);

:   f takes a string, and parses the integers out of it.  It's somewhat
:   expensive.

:   Now, how should I go about doing this?  Every solution I've been able
:   to come up with is a morally abhorrent kludge involving some large
:   number of static variables.  I really would like to be able to have a
:   block where I could define a few local temporaries, call f, and pass
:   those temps into the base constructor.  I could add another
:   constructor to A, but A shouldn't need to depend on f, since f is
:   really a part of B.  Also note that subclassing won't help, since the
:   arguments to A aren't functions of a common temporary, as f(x) and
:   f(x)+1 are in the original example.

:Do you ever use f for anything but to initialize or change the value
:of some A. If not, then f would really belong in A. In anycase, why
:not add A::A(const char *); that uses f to compute the 4 ints.

:Besides, Bjarne's proposal to use a protected init-method works well,
:too, iff A has a default constructor. Admittedly, things might get a
:little dirty if an A cannot have a meaningful state constructed out of
:nothing. In that case, one could (accidentally) use the value of an A
:before the object is fully constructed, so the problem would still be
:there.

:All in all, I don't see a reason to extend the language just because
:a new feature would occasionally save a few keystrokes.

:	[ more text deleted ]

You forgot to read the requirements of the problem.  The class A and the function
f could not be altered (probably due to a binary only license of some library).
The way to do this is of course by using MI, private inheritance and the
well-known dependence of base class declaration order ;-)

class A {
public:
  A(int a, int b, int c, int d);
  // ...
};

void f(const char* s, int* a, int* b, int* c, int* d);

class B_helper {
protected:
  int a, b, c, d;
  B_helper(const char* s) { f(s, &a, &b &c, &d); }
};

class B : private B_helper, public A {
public:
  B(const char* s) : B_helper(s), A(a, b, c, d) { /* usual code */ }
  // ...
};

This code is untested by me, but I did send this suggestion to Marc a few days
ago, and as he has not (yet) replied negatively, I suppose it works.  If it does,
this should be general enough to solve any problem of this kind, isn't it so?

Marc! I got your reply, have you tested this code yet?

						Niklas
-- 
Niklas Hallqvist	Phone: +46-(0)31-40 75 00
Applitron Datasystem	Fax:   +46-(0)31-83 39 50
Molndalsvagen 95	Email: niklas@appli.se
S-412 63  GOTEBORG, Sweden     mcsun!sunic!chalmers!appli!niklas