Topic: Unions containing objects w/ constructo


Author: tob@world.std.com (Tom O Breton)
Date: Sun, 12 Feb 1995 04:41:02 GMT
Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
> In article <D3rJzy.IF6@world.std.com> tob@world.std.com writes:
> >
> >Extending the struct ctor syntax with rules for unions (only one member
> >in the init-list) would be nice but not terribly important.
>
>         I do not quite agree: Here's why:
>
>         struct X {
>                 union { int x; double y; };
>                 X() : x(1), y(1.0) {}
>         };
>

The reason I think it's not terribly important is that, while the
absence of the rule lets you make a mistake, if you get it right in the
one place, you're OK. Also the error is easy to see and not a frequent
point of confusion.

[ From another message]

>         The above is a dirty summary, the full proposal
> is more pedantic. In fact, the rule is:
>
>         "A union is a class, a union of the form
>
>         union X {
>                 non-static-members
>                 other-members
>         };
>
> is immediately rewritten as:
>
>         struct {
>                 union { non-static-members };
>                 other-members
>         }

Neat.

        Tom

--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower





Author: clamage@Eng.Sun.COM (Steve Clamage)
Date: 9 Feb 1995 18:45:19 GMT
Raw View
In article MnH@world.std.com, tob@world.std.com (Tom O Breton) writes:
>mlg@scr.siemens.com (Michael Greenberg) writes:
>> Has there been any discussion about allowing unions to contain objects
>> with constructors or destructors?
>
>Not recently.
>
>> One approach would allow the union's constructor/destructor to call
>> the appropriate sub-object's constructor/destructor.
>
>I prefer a much simpler approach - dump typesafety for unions. Union
>ctors can of course already initialize whatever member you say it
>"really" is. Too bad dtors can't take parameters; in this case it could
>be useful.
>
>Here is another message I wrote on the subject:
>
>From: tob@world.std.com
>Subject: Wrong to try to make unions typesafe
>
>I think the decision to make unions disallow members with ctors was
>wrong and was contrary to the "trust the programmer" spirit of C.
>
>The thinking basically seems to be to make unions typesafe.

I see several problems with these ideas.

"Trust the programmer" is not the "spirit of C++". (Personally, I don't
trust myself not to make silly errors, and I prefer picky compilers.
Not everyone feels the same, evidently.)

The reason unions can't have members with ctors/dtors is that there is
no reasonable way to define semantics for them. Example:

 union Ugh {
  String s;
  fstream f;
  HashTable ht;
 };
 void foo()
 {
  Ugh z;
  ...
 }

Suppose String, fstream, and HashTable ctors/dtors all do memory management.
What ctor(s) get called when z is defined? What dtor(s) get called when
foo exits?

OK, let's be arbitrary and pick the C rule that the first member gets
initialized, so a String gets constructed. What happens when I store an
fstream or HashTable in z? Remember, the compiler must generate code to
destroy a String at foo's exit.

One could argue that for unions, no member ctors/dtors get invoked
automatically. Now we have special-case rules for member ctors/dtors,
depending on whether they are in unions or classes. But why bother?

I can't see any advantage in allowing this code to be written, since there
is nothing good that can come of it. If you really want the heterogeneity,
put pointers in the union, and do the ctors/dtors manually via new/delete.
(Placement-new, perhaps.) That avoids the uncertainty about what
initializations/cleanup will occur. (And you get to avoid type safety,
if that is a goal.)

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






Author: tob@world.std.com (Tom O Breton)
Date: Fri, 10 Feb 1995 02:52:46 GMT
Raw View
clamage@Eng.Sun.COM (Steve Clamage) writes:

> "Trust the programmer" is not the "spirit of C++". (Personally, I don't
> trust myself not to make silly errors, and I prefer picky compilers.
> Not everyone feels the same, evidently.)

If I take your meaning correctly, you are drawing a distinction between
the spirit of C and the spirit of C++, correct? I would argue that
unions are, by their nature, "raw" enough so the spirit of C still
holds.

> The reason unions can't have members with ctors/dtors is that there is
> no reasonable way to define semantics for them.
>
> Suppose String, fstream, and HashTable ctors/dtors all do memory management.
> What ctor(s) get called when z is defined?

The one that the union ctor says to. That was what I meant by "Union
ctors can of course already initialize whatever member you say it
"really" is. "

Presumably you provide enough ctors or enough ctor functionality to do
all the initializing you need.

        union Ugh
                {
                Ugh( char* makes_a_string )
                        {
                        new( this ) String( makes_a_string );
                        };

                Ugh( fstream& makes_an_fstream )
                        {
                        new( this ) fstream( makes_an_fstream );
                        };

                /* And let's get even a little fancier, to show what can
                be done. This much work should rarely be needed. */
                enum    what_type_ugh
                        { string_ugh, fstream_ugh, HashTable_ugh, };
                Ugh( int i, what_type_ugh t )
                        {
                        switch( t )
                                {
                                case string_ugh:
                                        new( this ) String( t );
                                        break;

                                //Let's say fstream can't be cted
                                //from an int.
                                case fstream_ugh
                                        assert( 0 );
                                        break;

                                //etc.
                                };
                        };

                String s;
                fstream f;
                HashTable ht;
                };

In other words, the knowledge of type would be handled by the union and
stop at the union boundaries. IMO that's as it should be.

Extending the struct ctor syntax with rules for unions (only one member
in the init-list) would be nice but not terribly important.

If this sounds like asking the programmer to take too much
responsibility, 0) that's kind of the idea with unions. The programmer
does take that responsibility in order to get the benefits. 1) it's a
lot less work, and even still safer, than pulling the data members from
whatever you want to include out into a C struct and using that.

> What dtor(s) get called when
> foo exits?

That was what I was talking about wrt wanting dtors to be able to take
parameters. Obviously such unions would no longer be able to destruct by
"falling off the end", which would be an error, but would need their
dtor called explicitly. It's a small price and rarely paid.

But right now you can just stick to classes with default dtors. Since
the classes one typically wants to put in unions tend to be data
interpreted in different ways, they tend to not have dtors so not a lot
is prohibited. So allowing unions containing classes with ctors but not
dtors is an interim solution.

So the supposition that one of the classes does resource management does
point to a need here, but in practice it's rare. At least for now, maybe
when template auto_ptr<T> becomes common it will not be.

> OK, let's be arbitrary and pick the C rule that the first member gets
> initialized, so a String gets constructed. What happens when I store an

Well, that's not what I was arguing for. In my vision the compiler would
never pick a member to init. If there are one or more members with
non-default ctors, the programmer will do it. In my vision unions can
still have ctors and dtors, and often would.

Perhaps I should have pointed out that in the actual example, existing
ctors were sufficient to initialize the union correctly, had other rules
permitted. I wanted to keep my example code short, so I omitted that.
Perhaps that was a mistake.

> I can't see any advantage in allowing this code to be written, since there
> is nothing good that can come of it. If you really want the heterogeneity,
> put pointers in the union, and do the ctors/dtors manually via new/delete.

Overhead: A level of indirection, another memory allocation, another
deletion.

In the specific example I gave, in my real code that would be
prohibitive. Something like 100 of them have to be dealt with every
screen refresh, so 100 * N extra machine instructions for extra
indirection. Also, there are a couple thousand of them kicking around in
memory, so it's a noticeable extra load on the heap, even with freelist
allocators. Had it not been so, I would have simply used a struct and
kissed off the wasted memory.

> One could argue that for unions, no member ctors/dtors get invoked
> automatically. Now we have special-case rules for member ctors/dtors,
> depending on whether they are in unions or classes. But why bother?

Unions already have rules distinct from rules for struct/class. Such as
what I'm proposing to remove.

I don't see why one would expect every member of a union to invoke it's
ctor as if it was a struct. Besides, what's so hard about defaulting to
not doing anything?

        Tom

--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower





Author: tob@world.std.com (Tom O Breton)
Date: Fri, 10 Feb 1995 05:13:48 GMT
Raw View
clamage@Eng.Sun.COM (Steve Clamage) writes:

> "Trust the programmer" is not the "spirit of C++". (Personally, I don't
> trust myself not to make silly errors, and I prefer picky compilers.
> Not everyone feels the same, evidently.)

If I take your meaning correctly, you are drawing a distinction between
the spirit of C and the spirit of C++, correct? I would argue that
unions are, by their nature, "raw" enough so the spirit of C still
holds.

> The reason unions can't have members with ctors/dtors is that there is
> no reasonable way to define semantics for them.
>
> Suppose String, fstream, and HashTable ctors/dtors all do memory management.
> What ctor(s) get called when z is defined?

The one that the union ctor says to. That was what I meant by "Union
ctors can of course already initialize whatever member you say it
"really" is. "

Presumably you provide enough ctors or enough ctor functionality to do
all the initializing you need.

        union Ugh
                {
                Ugh( char* makes_a_string )
                        {
                        new( this ) String( makes_a_string );
                        };

                Ugh( fstream& makes_an_fstream )
                        {
                        new( this ) fstream( makes_an_fstream );
                        };

                /* And let's get even a little fancier, to show what can
                be done. This much work should rarely be needed. */
                enum    what_type_ugh
                        { string_ugh, fstream_ugh, HashTable_ugh, };
                Ugh( int i, what_type_ugh t )
                        {
                        switch( t )
                                {
                                case string_ugh:
                                        new( this ) String( t );
                                        break;

                                //Let's say fstream can't be cted
                                //from an int.
                                case fstream_ugh
                                        assert( 0 );
                                        break;

                                //etc.
                                };
                        };

                String s;
                fstream f;
                HashTable ht;
                };

In other words, the knowledge of type would be handled by the union and
stop at the union boundaries. IMO that's as it should be.

Extending the struct ctor syntax with rules for unions (only one member
in the init-list) would be nice but not terribly important.

If this sounds like asking the programmer to take too much
responsibility, 0) that's kind of the idea with unions. The programmer
does take that responsibility in order to get the benefits. 1) it's a
lot less work, and even still safer, than pulling the data members from
whatever you want to include out into a C struct and using that.

> What dtor(s) get called when
> foo exits?

That was what I was talking about wrt wanting dtors to be able to take
parameters. Obviously such unions would no longer be able to destruct by
"falling off the end", which would be an error, but would need their
dtor called explicitly. It's a small price and rarely paid.

But right now you can just stick to classes with default dtors. Since
the classes one typically wants to put in unions tend to be data
interpreted in different ways, they tend to not have dtors so not a lot
is prohibited. So allowing unions containing classes with ctors but not
dtors is an interim solution.

So the supposition that one of the classes does resource management does
point to a need here, but in practice it's rare. At least for now, maybe
when template auto_ptr<T> becomes common it will not be.

> OK, let's be arbitrary and pick the C rule that the first member gets
> initialized, so a String gets constructed. What happens when I store an

Well, that's not what I was arguing for. In my vision the compiler would
never pick a member to init. If there are one or more members with
non-default ctors, the programmer will do it. In my vision unions can
still have ctors and dtors, and often would.

Perhaps I should have pointed out that in the actual example, existing
ctors were sufficient to initialize the union correctly, had other rules
permitted. I wanted to keep my example code short, so I omitted that.
Perhaps that was a mistake.

> I can't see any advantage in allowing this code to be written, since there
> is nothing good that can come of it. If you really want the heterogeneity,
> put pointers in the union, and do the ctors/dtors manually via new/delete.

Overhead: A level of indirection, another memory allocation, another
deletion.

In the specific example I gave, in my real code that would be
prohibitive. Something like 100 of them have to be dealt with every
screen refresh, so 100 * N extra machine instructions for extra
indirection. Also, there are a couple thousand of them kicking around in
memory, so it's a noticeable extra load on the heap, even with freelist
allocators. Had it not been so, I would have simply used a struct and
kissed off the wasted memory.

> One could argue that for unions, no member ctors/dtors get invoked
> automatically. Now we have special-case rules for member ctors/dtors,
> depending on whether they are in unions or classes. But why bother?

Unions already have rules distinct from rules for struct/class. Such as
what I'm proposing to remove.

I don't see why one would expect every member of a union to invoke it's
ctor as if it was a struct. Besides, what's so hard about defaulting to
not doing anything?

        Tom

--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower





Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 11 Feb 1995 10:58:50 GMT
Raw View
In article <D3rJzy.IF6@world.std.com> tob@world.std.com writes:
>
>Extending the struct ctor syntax with rules for unions (only one member
>in the init-list) would be nice but not terribly important.

 I do not quite agree: Here's why:

 struct X {
  union { int x; double y; };
  X() : x(1), y(1.0) {}
 };

Is the code well formed? The "union" here is not a union,
but an anonymous union, so that "x" an "y" are _in_
the scope of X. It is not clear if you can initialise
them (as shown) and if you can initialise more than
one of them (as shown).

An ISO International Standard should be clear and pedantic on this.
(By default, there is no error, but in that case it the behaviour
must be defined in the Standard)

The rule "at most one member may be initialised" makes sense here,
and can be applied to unions with constructible members too.

So, we need the rule anyhow. The extension to constructible
members then becomes trivial. The extension to permit
bases and virtual functions also follows.

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,
        81A Glebe Point Rd, GLEBE   Mem: SA IT/9/22,SC22/WG21
        NSW 2037, AUSTRALIA     Phone: 61-2-566-2189