elided copy constructors

elided copy constructors

Post by E. Robert Tisdal » Thu, 04 Nov 1999 04:00:00



I need your opinion about whether or not
a copy constructor for a base class may be elided
when a constructor for the derived class
is defined inline.  For example

        $ cat elide.cc
        #include<iomanip.h>

        class base {
          int   I;
        public:
          explicit
                base(int i): I(i) { }
                base(const base& b): I(b.I) {
            cerr << "copy constructor called" << endl; }
          int   value(void) const { return I; }
          };

        inline
        ostream&
                operator << (ostream& os, const base& b) {
           return os << b.value(); }

        class derived: public base {
        public:
                derived(const base& b): base(b) { }
          };

        int
        main() {
          derived d = base(3);
          cout << d << " = d" << endl;
          return 0;
          }

        $ g++ -O2 -o elide elide.cc
        $ elide
        copy constructor called
        3 = d
        $

Of course, my GNU C++ compiler
does not elide the copy constructor.
But does the standard permit an optimizing C++ compiler
to omit the copy constructor in this case?

Thanks in advance,


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]

 
 
 

elided copy constructors

Post by J.Barfurt » Thu, 04 Nov 1999 04:00:00




Quote:> I need your opinion about whether or not
> a copy constructor for a base class may be elided
> when a constructor for the derived class
> is defined inline.  For example
>         class base {
>           int   I;
>         public:
>           explicit base(int i): I(i) { }
>           base(const base& b): I(b.I) { cerr << "copy constructor called"
<< endl; }
>           int   value(void) const { return I; }
>          };
...
>         class derived: public base {
>         public:
>                 derived(const base& b): base(b) { }
>           };

>         int
>         main() {
>           derived d = base(3);
>           cout << d << " = d" << endl;
>           return 0;
>           }

>         $ g++ -O2 -o elide elide.cc
>         $ elide
>         copy constructor called
>         3 = d
>         $

> Of course, my GNU C++ compiler
> does not elide the copy constructor.
> But does the standard permit an optimizing C++ compiler
> to omit the copy constructor in this case?

Your compiler does elide the copy constructor (for class derived). You would
see two calls of the base copy constructor otherwise.

AFAIK it may not elide the constructor of 'derived' invoked by direct
initialization. As that constructor explicitly calls the copy c'tor of base,
that call may not be elided either.
The only exception would be under the as-if-rule: If the copy constructor of
base had no side effects (*) and the compiler can determine that this is the
case, it might not call the copy constructor of base. This would usually
occur only if the definition of base::base(base const&) is visible at the
point of call.
I suppose most compilers would do this optimization only if (as in your
sample) everything is defined inline and the constructor(s) of base involved
are sufficiently simple. But after inlining + optimization you probably
wouldn't see any differences in generated code either way, as constructing
and copying a 'base' object can be reduced to loading an integer into a
register.

(*) Of course your sample copy c'tor has a side effect: it writes to cerr.
Therefore it cannot be elided when direct-initializing a derived object.

BTW: In the compiler I use, the elision of copy constructors is done by the
front end and so is not affected by optimisation options. So you might try
writing a copy constructor for base without side effects and watch for calls
to it in a de*.(AFAIK how a program behaves in a de* is not part
of the programs output. So that needn't adhere to the as-if-rule.)
I would be very surprised though, if I put a breakpoint on the 'base' copy
c'tor and it weren't hit. (Except maybe in a highly optimized build, where
source-level debugging often breaks anyway). I guess most vendors of
compilers/source-level de*s would (and IMHO should) avoid surprising
their customers this way.

- J?rg

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.veryComputer.com/++/faq.html              ]

 
 
 

elided copy constructors

Post by Bill Wad » Fri, 05 Nov 1999 04:00:00





>>         class derived: public base {
>>         public:
>>                 derived(const base& b): base(b) { }
>>           };

>>         int
>>         main() {
>>           derived d = base(3);
>>           }
>AFAIK it may not elide the constructor of 'derived' invoked by direct
>initialization. As that constructor explicitly calls the copy c'tor of
base,
>that call may not be elided either.

I believe 12.8/15 seems to allow the copy c'tor of base to be skipped, even
if it isn't inline and has side effects and is explicitly called:

"Whenever a temporary object ... is copied ... an implementation is
permitted to treat [the two objects as aliases for each other] and not
perform the copy at all, even if [there would be side effects].

I don't see anything in this example which prevents the compiler from
writing (for its own use)
    derived::derived(int i): base(i){}
and changing the line in main to
   derived d(3);

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]

 
 
 

elided copy constructors

Post by J.Barfurt » Sat, 06 Nov 1999 04:00:00







> >>         class derived: public base {
> >>         public:
> >>                 derived(const base& b): base(b) { }
> >>           };

> >>         int
> >>         main() {
> >>           derived d = base(3);
> >>           }
> I believe 12.8/15 seems to allow the copy c'tor of base to be skipped,
even
> if it isn't inline and has side effects and is explicitly called:
> "Whenever a temporary object ... is copied ... an implementation is
> permitted to treat [the two objects as aliases for each other] and not
> perform the copy at all, even if [there would be side effects].

The temporary object is of type 'class base' and so would be any copy of it.
Direct-initializing a derived with a base is different from copying a base.
The constructor derived::derived(base const&) constructs a derived object.
It just so happens, that the base object passed as a parameter (no
temporaries visible here - instead we have a (const) reference such object)
is then copied into the base subobject of derived.
Whether or not there is a copy construction that can be skipped can always
be determined seeing only the class interfaces. The intent of allowing to
skip copy c'tors even if side effects are present seems to be just that.
Generally the (range of possible) meaning(s) of a program is supposed not to
depend on use of 'inline' nor whether the compiler actually performs the
inlining, nor on whether function definitions are in the same translation
unit [or even translated at the same time].

Quote:> I don't see anything in this example which prevents the compiler from
> writing (for its own use)
>     derived::derived(int i): base(i){}
> and changing the line in main to
>    derived d(3);

This 'optimization' would only be possible, if the definition of
derived::derived(base const&) was available to the compiler when translating
the line in main. As explicated above this difference is never allowed to
change the meaning of the program, especially not its output.

-- J?rg
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]

 
 
 

elided copy constructors

Post by Bill Wad » Sat, 06 Nov 1999 04:00:00






:> >>         class derived: public base {
:> >>         public:
:> >>                 derived(const base& b): base(b) { }
:> >>           };
:> >>
:> >>         int
:> >>         main() {
:> >>           derived d = base(3);
:> >>           }

:> I believe 12.8/15 seems to allow the copy c'tor of base to be skipped, even
:> if it isn't inline and has side effects and is explicitly called:

:> "Whenever a temporary object ... is copied ... an implementation is
:> permitted to treat [the two objects as aliases for each other] and not
:> perform the copy at all, even if [there would be side effects].

:The temporary object is of type 'class base' and so would be any copy of it.
:Direct-initializing a derived with a base is different from copying a base.
:The constructor derived::derived(base const&) constructs a derived object.

I agree entirely with the previous paragraph.  derived(base const&) must be
called.  If its body were changed to write "Hello" to cout, that would also
be required.

:It just so happens, that the base object passed as a parameter (no
:temporaries visible here - instead we have a (const) reference such object)
:is then copied into the base subobject of derived.

Here I disagree.  A "complete" temporary base object is being copied into a
base sub-object.  If a translator is smart enough to detect this it should
be able to take advantage of 12.8/15.  The existance of a named reference to
the object should, IMO, have no bearing.

:Whether or not there is a copy construction that can be skipped can always
:be determined seeing only the class interfaces. The intent of allowing to
:skip copy c'tors even if side effects are present seems to be just that.

Right.  base::base(base&) may be skipped no matter its implementation.  If
it performs a normal copy, does nothing at all, or calls abort() has no
effect on whether or not the compiler is allowed to elide the copy.

:Generally the (range of possible) meaning(s) of a program is supposed not to
:depend on use of 'inline' nor whether the compiler actually performs the
:inlining, nor on whether function definitions are in the same translation
:unit [or even translated at the same time].

Correct.  However in a few cases (this paragraph, sharing of string
literals), the standard gives an implementation license to perform
optimizations which may change the meaning of a program.

:> I don't see anything in this example which prevents the compiler from
:> writing (for its own use)
:>     derived::derived(int i): base(i){}
:> and changing the line in main to
:>    derived d(3);
:This 'optimization' would only be possible, if the definition of
:derived::derived(base const&) was available to the compiler when translating
:the line in main.

Correct.  In the example I quoted the definition of the constructor was
available.

:As explicated above this difference is never allowed to
:change the meaning of the program, especially not its output.

I read the standard differently.

Consider

  struct foo { foo(const foo&){ cout<<"copy"; }};

I hope we both agree that for
  foo f = foo();
there need not be any output.

Now let's declare:
 const foo& X(const foo&);
and write
  foo f = X(foo());
I think we both agree that if the definition of X can't be found at the time
this line is translated, we should expect to see some output at runtime.

Now suppose the translator can verify (perhaps because of a visible
definition) that X is written as
  const foo& X(const foo& g){ DoSomeOtherStuff(); return g; }
then I would argue that
  foo f = X(foo());
is not required to call foo's copy constructor.  It is, in general, required
to call DoSomeOtherStuff.

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]

 
 
 

elided copy constructors

Post by J.Barfurt » Tue, 09 Nov 1999 04:00:00







> :> I believe 12.8/15 seems to allow the copy c'tor of base to be skipped, even
> :> if it isn't inline and has side effects and is explicitly called:

> :> "Whenever a temporary object ... is copied ... an implementation is
> :> permitted to treat [the two objects as aliases for each other] and not
> :> perform the copy at all, even if [there would be side effects].
> Here I disagree.  A "complete" temporary base object is being copied into a
> base sub-object.  If a translator is smart enough to detect this it should
> be able to take advantage of 12.8/15.  The existance of a named reference to
> the object should, IMO, have no bearing.

Following your argument, we must conclude that in the following

    struct A
    {
        mutable int value;
        explicit A(int i) : value(i) {}
        void mutate(int i) const { value = i; }
    };

    int foo()
    {
        A const& temporary = A(1);

        A named(temporary);

        temporary.mutate(2);

        return named.value;
    }

it is unspecified, whether foo() will return 1 or 2, as temporary may be an
alias for named ?!

I doubt, that this (or the other case we discussed before, where the
'temporary-ness' even crosses a function call boundary) is what was intended
by 12.8/15. I have to admit though, that I can't find normative wording
against it.

The point is that, if a temporary object retains its state as temporary even
across (multiple) sequence points, the effects of this 'optimization' may
become non-local.

Quote:> :Whether or not there is a copy construction that can be skipped can always
> :be determined seeing only the class interfaces. The intent of allowing to
> :skip copy c'tors even if side effects are present seems to be just that.

IMHO it _should_ be that way, at least.

Quote:> Right.  base::base(base&) may be skipped no matter its implementation.  If
> it performs a normal copy, does nothing at all, or calls abort() has no
> effect on whether or not the compiler is allowed to elide the copy.

> :Generally the (range of possible) meaning(s) of a program is supposed not to
> :depend on use of 'inline' nor whether the compiler actually performs the
> :inlining, nor on whether function definitions are in the same translation
> :unit [or even translated at the same time].

> Correct.  However in a few cases (this paragraph, sharing of string
> literals), the standard gives an implementation license to perform
> optimizations which may change the meaning of a program.

> :> I don't see anything in this example which prevents the compiler from
> :> writing (for its own use)
> :>     derived::derived(int i): base(i){}
> :> and changing the line in main to
> :>    derived d(3);
> :This 'optimization' would only be possible, if the definition of
> :derived::derived(base const&) was available to the compiler when translating
> :the line in main.

> Correct.  In the example I quoted the definition of the constructor was
> available.

> :As explicated above this difference is never allowed to

                                                        ^
make that: should never be....

- Show quoted text -

Quote:> :change the meaning of the program, especially not its output.

> I read the standard differently.

> Consider

>   struct foo { foo(const foo&){ cout<<"copy"; }};

> I hope we both agree that for
>   foo f = foo();
> there need not be any output.

> Now let's declare:
>  const foo& X(const foo&);
> and write
>   foo f = X(foo());
> I think we both agree that if the definition of X can't be found at the
time
> this line is translated, we should expect to see some output at runtime.

As there are compilers (IBM VisualAge , afaik) that transcend the concept of
translation unit, you could (almost) never tell whether that definition is
visible. The optimization could even be done at link time if we have a very
smart linker.

Quote:> Now suppose the translator can verify (perhaps because of a visible
> definition) that X is written as
>   const foo& X(const foo& g){ DoSomeOtherStuff(); return g; }
> then I would argue that
>   foo f = X(foo());
> is not required to call foo's copy constructor.  It is, in general,
required
> to call DoSomeOtherStuff.

-- J?rg

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]

 
 
 

elided copy constructors

Post by Bill Wad » Tue, 09 Nov 1999 04:00:00



>Following your argument, we must conclude that in the following

>    struct A
>    {
>        mutable int value;
>        explicit A(int i) : value(i) {}
>        void mutate(int i) const { value = i; }
>    };

>    int foo()
>    {
>        A const& temporary = A(1);

>        A named(temporary);

>        temporary.mutate(2);

>        return named.value;
>    }

>it is unspecified, whether foo() will return 1 or 2, as temporary may be an
>alias for named ?!

>I doubt, that this (or the other case we discussed before, where the
>'temporary-ness' even crosses a function call boundary) is what was
intended
>by 12.8/15. I have to admit though, that I can't find normative wording
>against it.

>The point is that, if a temporary object retains its state as temporary
even
>across (multiple) sequence points, the effects of this 'optimization' may
>become non-local.

I hadn't taken it that far, but I agree that it looks like a defect in the
standard.  Perhaps the simplest fix is that named temporaries (that is
temporaries for which a named reference exists) should not be subject to
12.8/15.  The rules were changed between CD2 and the standard.  In CD2 it
didn't matter if the source was a temporary, it only mattered that the
implementation could prove that the only one of the objects would be "used"
in the future.  I believe the intent of the change was to increase safety
when named variables were passed as arguments to functions.

I'll repost a "Defect Report" style post.

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]

[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]