Topic: Constructor Limitation: [Was: Constructor question]


Author: dsouza@gwen.cad.mcc.com (Desmond Dsouza)
Date: 3 May 91 20:03:07 GMT
Raw View
I'm cross posting this to comp.std.c++ because I see this as a BASIC
limitation of the constructor syntax for member initialization and have
not seen this discussed before.

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

  > [... good reasons for not using placement syntax...]
  > Again, if you want to factor out common code from constructors, I recommend
  > you follow Bjarne's suggestion: write a private init() routine which is
  > called from within each constructor.

Sorry, but I dont think this works in general.

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.

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 ???
: a1 (F1(j), F1(j)+1)
{
  // this part does not help: I cannot modify a1 at this point.
}


// 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.

--

-------------------------------------------------------------------------------
 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




Author: jamshid@ut-emx.uucp (Jamshid Afshar)
Date: 17 May 91 07:25:55 GMT
Raw View
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




Author: cimshop!davidm@uunet.UU.NET (David S. Masterson)
Date: 17 May 91 17:41:59 GMT
Raw View
>>>>> 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!"




Author: dsouza@gwen.cad.mcc.com (Desmond Dsouza)
Date: 20 May 91 20:44:06 GMT
Raw View
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




Author: jimad@microsoft.UUCP (Jim ADCOCK)
Date: 20 May 91 20:59:57 GMT
Raw View
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.
}




Author: dsouza@gwen.cad.mcc.com (Desmond Dsouza)
Date: 22 May 91 18:19:58 GMT
Raw View
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




Author: euaeny@eua.ericsson.se (Erik Nyquist)
Date: 23 May 91 17:05:30 GMT
Raw View
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.




Author: marc@mit.edu (Marc Horowitz)
Date: 27 May 91 18:09:43 GMT
Raw View
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




Author: pena@brainware.fi (Olli-Matti Penttinen)
Date: 29 May 91 18:51:38 GMT
Raw View
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




Author: niklas@appli.se (Niklas Hallqvist)
Date: 2 Jun 91 01:33:01 GMT
Raw View
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