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)