Topic: Cleanup using dtors


Author: clamage@Eng.Sun.COM (Steve Clamage)
Date: 14 Sep 1994 21:00:29 GMT
Raw View
In article 140994143214@128.253.162.6, joel@eddie.fac.cornell.edu (Joel Bender) writes:
>
>When I look at:
>
>    (1)    X x = y;
>
>I read this as:
>
>    (2)    X x = (X)y;
>
>Which to me is as readable as:
>
>    (3)    X x;
>    (3)    x = (X)y;

Readability aside, 1 and 2 are equivalent, and 3 means something different.

>First, the construction of x in (1) does not have any parameters, so
>X(void)
>must be public.  This is consistent with the declaration in line 1 of (3).

You are confusing assignment with initialization; in C++ they are different.

Initialization means giving a value to a newly-created object. 1 and 2
are examples of initialization, as is
 (4) X x(y); // initialize x with the value of y

Assignment means giving a possibly new value to an existing object. In 3,
x already exists when it is assigned the new value of y.

For C-like objects, the distinction is not important. For C++ class objects,
different member functions are used, and the semantics may be entirely
different. One point of confusion is that the same "=" is used for both.

In 1 and 2, the value of y is converted to an X, meaning that a variation of
X::X(Y) or Y::operator X() must exist. This value is then used to initialize x.

In 3, X::X() must exist (if only implicitly) to create x. Then the value
of y is converted to an X (as above) and assigned to x via an assignment
operator such as X::operator=(const X&).

---
Steve Clamage, stephen.clamage@eng.sun.com






Author: joel@eddie.fac.cornell.edu (Joel Bender)
Date: 14 Sep 1994 18:48:32 GMT
Raw View
All this wonderful discussion has brought me to a related question.

>> In article <CBARBER.94Sep8111500@apricot.bbn.com> cbarber@bbn.com
>> (Christopher Barber) writes:
>>
>> |> I thought that according to the 'standard'
>> |> the two initialization forms were supposed to be identical:
>>
>> |> X x = y ;
>> |> X x(y) ;
>>
>> This is a common misconception.  The first form is by definition
>> (according to the ARM and the latest draft of the standard) the same
>> as:
>>
>>  X x( X( y ) ) ;

When I look at:

    (1)    X x = y;

I read this as:

    (2)    X x = (X)y;

Which to me is as readable as:

    (3)    X x;
    (3)    x = (X)y;

This could mean a whole bunch of things, some of which I could be very
wrong
about.  Please consider this as what goes on in my head!  :-)

First, the construction of x in (1) does not have any parameters, so
X(void)
must be public.  This is consistent with the declaration in line 1 of (3).

Second, the conversion of y to type X has to be available, so operator X(Y
y); must be public.  This conversion mechanism may be provided by default.

Third, the copy constructor for X must be public.  This may be provided by
default.

Assuming I've got all that right (ha!), a programmer may provide
X::operator=(Y) and the conversion isn't necessary.  The code in (1)
becomes:

    (4)    X x;
    (4)    x.operator=(y);

And everyone is happy.

>  ArrayOf< int >  a( 20 ) ; //  legal
>  ArrayOf< int >  a = 20 ; //  illegal
>
> I personally prefer it this way.  The declaration *constructs* an
> array of 20 int's, it does not in some magic way convert the integer
> value 20 to an array.  In my mind, the second notation somehow
> suggests the latter.

So far I agree, the latter gives me the creeps.

> I might add that I almost never use the assignment form of
> initialization, since I find it looks too much like an assignment.  So
> I may be prejudiced in my point of view.

This is the foot in the side of the head...IT'S NOT ASSIGNMENT?

--
Joel Bender               joel@eddie.fac.cornell.edu
Programmer/Analyst        Voice: (607) 255-8880
Utilities Department      FAX:   (607) 255-????
Cornell University




Author: kanze@us-es.sel.de (James Kanze US/ESC 60/3/164 #71425)
Date: 12 Sep 1994 13:55:40 GMT
Raw View
In article <CBARBER.94Sep9131002@apricot.bbn.com> cbarber@bbn.com
(Christopher Barber) writes:

|> >>>>> "JK" == James Kanze US/ESC 60/3/164 #71425 <kanze@us-es.sel.de>:
|> >>>>> "CB" == Christopher Barber <cbarber@bbn.com>:

|>     CB> I thought that according to the 'standard'
|>     CB> the two initialization forms were supposed to be
|>     CB> identical:

|>     CB>    X x = y ;
|>     CB>    X x(y) ;

|>     JK> This is a common misconception.  The first form is by
|>     JK> definition (according to the ARM and the latest draft of the standard)
|>     JK> the same as:

|>     JK>     X x( X( y ) ) ;

|>     CB> Ok.  I just went back and found the appropriate section in the
|>     CB> ARM, but as I pointed out before, the example in 12.2c would suggest
|>     CB> that even Stroustrup and Ellis are capable of forgetting this piece
|>     CB> of trivia.

|>     JK> Its not so much forgetting it, as recognizing that it almost always
|>     JK> makes no difference.  Most classes do have an accessible copy
|>     JK> constructor, and I know of no compiler which doesn't elide the
|>     JK> temporary.

|> Except that in many cases it *does* matter, as is the case of the
|> Reaper class I posted.  I have written several classes for which
|> copying was forbidden and find this definition highly annoying.
|> My compiler does do the right thing but generates a warning and
|> leads me to fear that a future version might issue an error instead.

I also have a number of classes for which copying is forbidden.

However, I have gotten into the habit of using the "X x( y )" style of
initialization, perhaps because I also frequently have more than one
parameter, and in such cases, this is the only style permitted.

|>     CB> |> Can anyone tell me the rationale for [this]?

|>     JK> The question was discussed in the standards committee.  I forget
|>     JK> all of the discussion, but basically...  (The following represents my
|>     JK> personal position after having followed the discussion, and is
|>     JK> *not* a summary of the discussion, nor does it represent any
|>     JK> consensus from the standards committtee.)

|>     JK> 1. There *are* two types of construction, independantly of
|>     JK> declaration, and these types have different requirements.
|>     JK> Basically, explicitly invoking a constructor ("X()") is just that,
|>     JK> you get the constructor you asked for, whereas it seems more
|>     JK>  logical to require the copy constructor for passing parameters.
|>     JK> After all, if you declare a function "f( X )", you wouldn't expect
|>     JK> that it would be legal to pass it a Y (because there is a
|>     JK> constructor "X(Y)), but *not* an X (because X has no copy
|>     JK> constructor).

|> This isn't a very compelling argument to me.  It does not seem "logical"
|> to me that a superflous object and copy operation is inserted here at
|> all.  When I see,

I agree that it isn't "compelling", in the sense that any other point
of view wouldn't make sense.  But in a similar manner, I don't find
the counter arguments "compelling", either, and so this is an adequate
reason *for* *me*.

Obviously, the relative weights one puts on the various non-compelling
reasons will depend on coding style.

|>    X x = 4 ;

|> I don't expect that 4 will converted to an X and then copied any
|> more than I would expect it to be in the following situation:

|>    class X {
|>        X (const X&) ;
|>        void operator= (const X &) ;
|>    public:
|>        X() ;
|>        X(int) ;
|>        void operator= (int) ;
|>    }

|>    X x ;
|>    x = 4 ;

Hmmm.  The above is rather untypical, don't you think.

I think that the real analogy here should be with the built-in types.
If I write:

 int                 i ;
 i = 2.5 ;

2.5 *is* converted into an int before assignment.  (Actually, one
could also argue that there was a built-in int::operator=( double ).
But I don't think that this is the way most people think of the
language.)

Of course, this isn't compelling, but it is an indication as to why
one might have a small preference for the rule that you find so
unnatural.

|> This is exactly the kind of thing that newcomers to the language
|> complain about.  I don't understand why this is not changed since
|> I can't think of how it would break any existing code to do it and
|> it would remove a potential source of confusion.

I doubt it would break code, since it would make legal things that
aren't currently legal.

It would make it more difficult to explain the initialization of
function parameters and return values, since they would use a type of
initialization that occurs no where else.
--
James Kanze      Tel.: (+33) 88 14 49 00     email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                              -- Beratung in industrieller Datenverarbeitung






Author: hopps@mmm.com (Kevin J Hopps)
Date: 12 Sep 1994 13:23:41 GMT
Raw View
Christopher Barber (cbarber@bbn.com) wrote:
> ...

> This isn't a very compelling argument to me.  It does not seem "logical"
> to me that a superflous object and copy operation is inserted here at
> all.  When I see,

>    X x = 4 ;

> I don't expect that 4 will converted to an X and then copied any
> more than I would expect it to be in the following situation:

>    class X {
>        X (const X&) ;
>        void operator= (const X &) ;
>    public:
>        X() ;
>        X(int) ;
>        void operator= (int) ;
>    }
>
>    X x ;
>    x = 4 ;

> This is exactly the kind of thing that newcomers to the language
> complain about.  I don't understand why this is not changed since
> I can't think of how it would break any existing code to do it and
> it would remove a potential source of confusion.

I agree, and I was surprised by exactly the same thing when I wrote my
own smart pointer class.  I had expected that
    Ptr<T> tp = new T;
would be an abbreviation for
    Ptr<T> tp;
    tp = new T;

I was very disappointed.  The first form is quite natural, especially
for people coming from a C background.  Since my class was to be used
by people like that, I wanted to allow the first syntax.  This forced
me to write a very ugly copy constructor when I preferred to make the
copy constructor private.
--
Kevin J. Hopps                  e-mail: kjhopps@mmm.com
3M Company                      phone:  (612) 737-3300
3M Center, Bldg. 235-2D-45      fax:    (612) 737-2700
St. Paul, MN 55144-1000         Opinions are my own.  I don't speak for 3M.




Author: cbarber@bbn.com (Christopher Barber)
Date: 08 Sep 1994 15:15:00 GMT
Raw View
>>>>> "FH" == Fergus Henderson <fjh@munta.cs.mu.OZ.AU> writes:

    FH> cbarber@bbn.com (Christopher Barber) writes:
    >> For instance, here is a very simple template
    >> class for managing allocated memory:
    >>
    >> template <class T>
    >> class Reaper {
    >> public:
    >> Reaper(T *ptr) : ptr_(ptr) {};
    >> ~Reaper() { if (ptr_) delete ptr_; };
    >> operator T* () { return ptr_; };
    >> T* operator->() { return ptr_; };
    >>
    >> private:
    >> T *ptr_ ;
    >> };

    FH> That class is *too* simple.  You need to declare a copy-constructor
    FH> and an assignment operator (perhaps private).

Yes, I had declared them private in the actual implementation.  I
left them out of the example.

    >> void f()
    >> {
    >> Reaper<Foo> *foo = new Foo() ;

    FH> That's a bug or two.  Firstly, `foo' should be an object, not a
    FH> pointer.

Mea culpa.

    FH> It should be

    FH>  Reaper<Foo> foo = new Foo() ;

    FH> Secondly, the above may (depending on compiler optimization)
    FH> construct a temporary Reaper<Foo> object, and then copy that to
    FH> `foo' using the copy-constructor.

Why should it do that?  I thought that according to the 'standard'
the two initialization forms were supposed to be identical:

   X x = y ;
   X x(y) ;

The fact that some compilers may behave as you suggest I would consider
to be a bug.  At the very least, it would be retarded behavior for the
compiler to create an obviously superflous temporary.  In fact, there is
the following example from the ARM (sec 12.2c - p 304 in 1st ed):

  class X {
     void operator=(X&);
     X(X&);
  public:
     X(int);
  };

  X f(X a)
  {
     return a;     // error: X::X(X&) private
  }

  void g()
  {
     X a=0;
     f(a);          // error: X::X(X&) private
  }

Note that there is no error comment on the line "X a=0;" implying that this
usage is perfectly ok.  Of course, the actual draft standard or later
versions of the ARM might say something more precise about this point, but
I can't think of any good reason for the language to allow the compiler to
use a temporary and copy constructor in this instance.

Clearly, in the case of the Reaper class, one would not want to allow
copying, since this could result in the same pointer being freed more than
once.  It seems unfair to require the class user to avoid the use of the
"X x = y" syntax on such flimsy grounds.

- Chris

--
Christopher Barber
(cbarber@bbn.com)




Author: kanze@us-es.sel.de (James Kanze US/ESC 60/3/164 #71425)
Date: 08 Sep 1994 16:35:01 GMT
Raw View
In article <CBARBER.94Sep8111500@apricot.bbn.com> cbarber@bbn.com
(Christopher Barber) writes:

|>     FH> It should be

|>     FH>  Reaper<Foo> foo = new Foo() ;

|>     FH> Secondly, the above may (depending on compiler optimization)
|>     FH> construct a temporary Reaper<Foo> object, and then copy that to
|>     FH> `foo' using the copy-constructor.

|> Why should it do that?  I thought that according to the 'standard'
|> the two initialization forms were supposed to be identical:

|>    X x = y ;
|>    X x(y) ;

This is a common misconception.  The first form is by definition
(according to the ARM and the latest draft of the standard) the same
as:

 X x( X( y ) ) ;

That is: construct a temporary X from y, and copy it (using the copy
constructor) into x.  So there must be an accessible copy constructor
(eventually provided by the compiler).  In the ARM, a compiler is
explicitly allowed to optimize the temporary away, but only if the
statement is legal *before* the optimization.  (The degree of
optimization may not change the legality of a program.)

BTW, the version of the definition which I wrote is for the purposes
of this example, only.  In fact, it does *not* declare an instance of
X, initialized with a y, but rather a function (implicitly extern)
returning an X and taking an X as a parameter.  If you really want to
write something like this (and if the argument to the constructor were
a class different from X, you might), use something like the
following:

 X x( (X)( y ) ) ;

Isn't C++ wonderful.
--
James Kanze                                  email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue des Francs Bourgeois, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                              -- Beratung in industrieller Datenverarbeitung




Author: cbarber@bbn.com (Christopher Barber)
Date: 08 Sep 1994 21:46:56 GMT
Raw View
James Kanze US/ESC 60/3/164 #71425 <kanze@us-es.sel.de> writes:

    >> In article <CBARBER.94Sep8111500@apricot.bbn.com> cbarber@bbn.com
    >> (Christopher Barber) writes:

    >> |> I thought that according to the 'standard'
    >> |> the two initialization forms were supposed to be identical:

    >> |> X x = y ;
    >> |> X x(y) ;

    >> This is a common misconception.  The first form is by definition
    >> (according to the ARM and the latest draft of the standard) the same
    >> as:

    >>  X x( X( y ) ) ;

Ok.  I just went back and found the appropriate section in the ARM,
but as I pointed out before, the example in 12.2c would suggest that
even Stroustrup and Ellis are capable of forgetting this piece of
trivia.

Can anyone tell me the rationale for defining "X x = y" in this
way as opposed to just saying that it translates to "X x(y)"?
Is it just left over cruft from cfront days?  Is it too late to
suggest that this be changed in the standard?

- Chris
--
Christopher Barber
(cbarber@bbn.com)




Author: nessus@mit.edu (Douglas Alan)
Date: Thu, 8 Sep 1994 22:31:11 GMT
Raw View
In article <CBARBER.94Sep8111500@apricot.bbn.com> cbarber@bbn.com
(Christopher Barber) writes:

>   Why should it do that?  I thought that according to the 'standard'
>   the two initialization forms were supposed to be identical:

>      X x = y ;
>      X x(y) ;

>   The fact that some compilers may behave as you suggest I would consider
>   to be a bug.  At the very least, it would be retarded behavior for the
>   compiler to create an obviously superflous temporary.  In fact, there is
>   the following example from the ARM (sec 12.2c - p 304 in 1st ed): [...]

>   Note that there is no error comment on the line "X a=0;" implying that this
>   usage is perfectly ok.

Unfortunately, the ARM is not really as reasonable as you or I.  In
12.6.1 (p. 285) it *does* say what you fear:

     complex f = 3;          // construct complex(3) using
                             // complex(double)
                             // copy it into 'f'

     [X] For each of these, a compiler will be able to construct the
     complex value directly in the variable created (see 12.1, 12.8),
     but only for a and e is the compiler required to do so; in the
     other cases a temporary object may be used (12.2).   Note,
     however that access control is applied for the copy constructor (11),
     so had complex(const complex&) been private only the declarations
     of a and e would have been legal. []




Author: kanze@us-es.sel.de (James Kanze US/ESC 60/3/164 #71425)
Date: 09 Sep 1994 09:31:22 GMT
Raw View
In article <CBARBER.94Sep8174656@apricot.bbn.com> cbarber@bbn.com
(Christopher Barber) writes:

|> James Kanze US/ESC 60/3/164 #71425 <kanze@us-es.sel.de> writes:

|>     >> In article <CBARBER.94Sep8111500@apricot.bbn.com> cbarber@bbn.com
|>     >> (Christopher Barber) writes:

|>     >> |> I thought that according to the 'standard'
|>     >> |> the two initialization forms were supposed to be identical:

|>     >> |> X x = y ;
|>     >> |> X x(y) ;

|>     >> This is a common misconception.  The first form is by definition
|>     >> (according to the ARM and the latest draft of the standard) the same
|>     >> as:

|>     >>  X x( X( y ) ) ;

|> Ok.  I just went back and found the appropriate section in the ARM,
|> but as I pointed out before, the example in 12.2c would suggest that
|> even Stroustrup and Ellis are capable of forgetting this piece of
|> trivia.

Its not so much forgetting it, as recognizing that it almost always
makes no difference.  Most classes do have an accessible copy
constructor, and I know of no compiler which doesn't elide the
temporary.

|> Can anyone tell me the rationale for defining "X x = y" in this
|> way as opposed to just saying that it translates to "X x(y)"?
|> Is it just left over cruft from cfront days?  Is it too late to
|> suggest that this be changed in the standard?

The question was discussed in the standards committee.  I forget all
of the discussion, but basically...  (The following represents my
personal position after having followed the discussion, and is *not* a
summary of the discussion, nor does it represent any consensus from
the standards committtee.)

1. There *are* two types of construction, independantly of
declaration, and these types have different requirements.  Basically,
explicitly invoking a constructor ("X()") is just that, you get the
constructor you asked for, whereas it seems more logical to require
the copy constructor for passing parameters.  After all, if you
declare a function "f( X )", you wouldn't expect that it would be
legal to pass it a Y (because there is a constructor "X(Y)"), but
*not* an X (because X has no copy constructor).

2. Given that there are two types of construction, and two syntaxes
for declaration, it seems rational that each syntax use a different
type.  In this way, the programmer can choose what he wants.  Since
for the built-in types, there is no significant difference between
copying and assignment, it seems logical (to me, at least) that an
initialization sequence containing '=' "copy".

There has also been some discussion recently of introducing some way
of allowing the user to disallow the use of certain constructors for
type conversion.  A typical example is the case of an array of int
which takes a single int parameter specifying the length of the array.
It is typically an error to use this constructor for type conversion:
the semantics are not what one wants for this use.

If the committee finally does adapt something along these lines (not
really very likely, since the current status is just vague discussion,
and not yet even a formal proposal), then there will be a distinct
difference:

 ArrayOf< int >  a( 20 ) ; //  legal
 ArrayOf< int >  a = 20 ; //  illegal

I personally prefer it this way.  The declaration *constructs* an
array of 20 int's, it does not in some magic way convert the integer
value 20 to an array.  In my mind, the second notation somehow
suggests the latter.

I might add that I almost never use the assignment form of
initialization, since I find it looks too much like an assignment.  So
I may be prejudiced in my point of view.
--
James Kanze                                  email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue des Francs Bourgeois, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                              -- Beratung in industrieller Datenverarbeitung




Author: cbarber@bbn.com (Christopher Barber)
Date: 09 Sep 1994 17:10:02 GMT
Raw View
>>>>> "JK" == James Kanze US/ESC 60/3/164 #71425 <kanze@us-es.sel.de>:
>>>>> "CB" == Christopher Barber <cbarber@bbn.com>:

    CB> I thought that according to the 'standard'
    CB> the two initialization forms were supposed to be
    CB> identical:

    CB>    X x = y ;
    CB>    X x(y) ;

    JK> This is a common misconception.  The first form is by
    JK> definition (according to the ARM and the latest draft of the standard)
    JK> the same as:

    JK>     X x( X( y ) ) ;

    CB> Ok.  I just went back and found the appropriate section in the
    CB> ARM, but as I pointed out before, the example in 12.2c would suggest
    CB> that even Stroustrup and Ellis are capable of forgetting this piece
    CB> of trivia.

    JK> Its not so much forgetting it, as recognizing that it almost always
    JK> makes no difference.  Most classes do have an accessible copy
    JK> constructor, and I know of no compiler which doesn't elide the
    JK> temporary.

Except that in many cases it *does* matter, as is the case of the
Reaper class I posted.  I have written several classes for which
copying was forbidden and find this definition highly annoying.
My compiler does do the right thing but generates a warning and
leads me to fear that a future version might issue an error instead.

    CB> |> Can anyone tell me the rationale for [this]?

    JK> The question was discussed in the standards committee.  I forget
    JK> all of the discussion, but basically...  (The following represents my
    JK> personal position after having followed the discussion, and is
    JK> *not* a summary of the discussion, nor does it represent any
    JK> consensus from the standards committtee.)

    JK> 1. There *are* two types of construction, independantly of
    JK> declaration, and these types have different requirements.
    JK> Basically, explicitly invoking a constructor ("X()") is just that,
    JK> you get the constructor you asked for, whereas it seems more
    JK>  logical to require the copy constructor for passing parameters.
    JK> After all, if you declare a function "f( X )", you wouldn't expect
    JK> that it would be legal to pass it a Y (because there is a
    JK> constructor "X(Y)), but *not* an X (because X has no copy
    JK> constructor).

This isn't a very compelling argument to me.  It does not seem "logical"
to me that a superflous object and copy operation is inserted here at
all.  When I see,

   X x = 4 ;

I don't expect that 4 will converted to an X and then copied any
more than I would expect it to be in the following situation:

   class X {
       X (const X&) ;
       void operator= (const X &) ;
   public:
       X() ;
       X(int) ;
       void operator= (int) ;
   }

   X x ;
   x = 4 ;

This is exactly the kind of thing that newcomers to the language
complain about.  I don't understand why this is not changed since
I can't think of how it would break any existing code to do it and
it would remove a potential source of confusion.

- Chris



--
Christopher Barber
(cbarber@bbn.com)