Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The point of destroying a temporary object when it created in a member-initializer

#include <iostream>
struct A{
    A(int){
       
    }
    ~A(){
        std::cout<<"A destroy\n";
    }
};
struct B{
    B(int){
        std::cout<<"B construct\n";
    }
    ~B(){
        std::cout<<"B destroy\n";
    }
};
struct Content{
    A const& a;
};
struct Data{
    Data():c{0},b{0}{

    }
    Content c;
    B b;
};
int main(){
    Data d;
    std::cout<<"exit\n";
}

The output of GCC is:

B construct
A destroy
exit
B destroy

Clang complains this code is ill-formed. Here is the performance of two compilers.

About the error that Clang reports, there's indeed a relevant rule in the standard, that is:
[class.init#class.base.init-8]

A temporary expression bound to a reference member in a mem-initializer is ill-formed.

I'm not sure whether Clang has overunderstood it? In my mind, the rule seems to say that the reference member named by mem-initializer-id of the mem-initializer shouldn't be bound to a temporary expression. In my example, the member c of class Data is not a reference.

Presumably, Clang consider it as that any initialization of reference member which makes the reference member be bound to a temporary expression occurs in a member-initializer are all ill-formed. So I gave a example to examine whether Clang think so.

struct A{
  int const& rf;
};
struct B{
   B():a(new A{0}){}
   A* a;
};
int main(){
  B b;
  delete b.a;
}

It gives a warning but not an error. So, I'm not sure Clang think so. I don't know how does it understand the rule?

If the first example itself is valid, I will consider GCC does not obey the standard. Because the order of destroying the temporary object.

[class.temporary#4]

Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created.

The temporary object would be otherwise destroyed in the end of the full-expression which is the member-initializer, in my example, that is c{0}. However, GCC destroys the temporary object after the subobject b has constructed. I think here is a first issue.

Actually, the temporary be bound the the reference is not the exception listed here:
[class.temporary#6]

The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference
The exceptions to this lifetime rule are:

  • A temporary object bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.
  • A temporary object bound to a reference element of an aggregate of class type initialized from a parenthesized expression-list ([dcl.init]) persists until the completion of the full-expression containing the expression-list.
  • The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
  • A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.

Neither, that is, my first example is not exception listed in the above list. Hence, the lifetime of the temporary object should be the same as the lifetime of the subobject a of subobject c of the object b, they all have the same lifetime as that of b. So, why GCC destroy the temporary object so early? Shouldn't the temporary object be destroyed together with the object b in main? I think here is the second issue of GCC. I don't know how Clang process the temporary object, since it has gave an error earlier.

Question:

  1. Is that Clang reports an error for the first example Right? If it's right, Should [class.init#class.base.init-8] be more clear for that?

  2. If Clang overunderstand [class.init#class.base.init-8], Is the performance of GCC of destroying the temporary object be considered as a bug? Or, the exceptions omiss this case? Even though the exceptions omiss this case(reference binding occurs in member-initializer), I still think GCC has wrong, Shouldn't the temporary be destroyed at the end of full-expression(c{0}) which is sequenced before constructing b.

How to interpret these questions in the above?

like image 886
xmh0511 Avatar asked Dec 25 '20 05:12

xmh0511


1 Answers

Clang is correct, but yes, the Standard could be clearer.

The effect of [class.temporary]/6 (temporary expression lifetime extension by reference binding) is to ensure that, other than the listed exceptions, the lifetime of temporaries bound to references is extended to the lifetime of the reference. But, when the reference is a class non-static data member, the lifetime of the reference is not statically known at the point of binding (where this occurs in a (possibly defaulted) constructor), so it would be unreasonable for the temporary to be lifetime-extended. Since non-static data members are not included in the list of exceptions, this must be prevented by other means, and indeed it is, by the IF cases in [class.base.init]:

8 - A temporary expression bound to a reference member in a mem-initializer is ill-formed.

11 - A temporary expression bound to a reference member from a default member initializer is ill-formed.

We must conclude that the intent of this language is to render ill-formed any attempt to bind a temporary to a class data member from within the class's (possibly defaulted) constructor, since otherwise the temporary would qualify for lifetime extension, which would be nonsensical (where the lifetime of the reference is not known statically). This must therefore include reference members of sub-aggregates; it would be appropriate for the Standard to include a Note making this clear.

It is worthwhile to consider binding of temporary expressions to references as falling under 3 cases:

  1. IF under [class.base.init]/8 and /11 (class non-static data members)
  2. Exceptions under [class.temporary]/6; the temporary is destroyed at the end of the full-expression.
  3. Otherwise, lifetime extension occurs.

So, if a compiler does not reject, and the code does not fall under one of the exceptions in [class.temporary]/6, and the compiler does not perform lifetime extension (to the full lifetime of the reference), the compiler is at fault.

MSVC also, incorrectly, accepts your code and outputs:

main
A(int)
~A()
B(int)
D()
exit
~B()

An interesting case is when the class containing the reference non-static data member is an aggregate and thus qualifies for aggregate initialization via list-initialization syntax (example adapted from CWG 1815):

struct A {};
struct C { A&& a = A{}; };
C c1;         // #1
C c2{A{}};    // #2
C c3{};       // #3
C c4 = C();   // #4

Here #1 and #4 are ill-formed, though gcc and MSVC incorrectly accept. #3 is ill-formed per the current wording of the Standard per [class.base.init]/11, but this is contrary to the intent of the Committee as noted on CWG 1815:

Notes from the February, 2014 meeting:

CWG agreed with the suggested direction, which would treat #3 in the example like #2 and make the default constructor deleted

That is, #3 would be valid, and result in lifetime extension; gcc and clang do so, but MSVC fails to perform lifetime extension in either #2 or #3; icc performs lifetime extension for #2 only. Oddly, clang reports a diagnostic that claims it will not perform lifetime extension despite doing so!:

warning: sorry, lifetime extension of temporary created by aggregate initialization using default member initializer is not supported; lifetime of temporary will end at the end of the full-expression [-Wdangling]

I've asked Validity and/or lifetime extension of mem-initializer in aggregate initialization.

Note that (assuming this is intended to be allowed) it only works because c3 is a complete object; if it was a member object (like c in your first example) [class.base.init] would definitively apply and c3 would also be ill-formed.

like image 78
ecatmur Avatar answered Nov 11 '22 19:11

ecatmur