Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I always assumed, that temporary objects live until the end of a full-expression. Here is however a curious difference between initializations of a std::vector and an array.

Please consider the following code:

#include <iostream>
#include <vector>

struct ID{ 
  static int cnt;
  // the number of living object of class ID at the moment of creation:  
  int id;

  ID():id(++cnt){}

  ~ID(){
     cnt--;
  }
};

int ID::cnt=0;

int main(){

  int arr[]{ID().id, ID().id};
  std::vector<int> vec{ID().id, ID().id};

  std::cout<<" Array: "<<arr[0]<<", "<<arr[1]<<"
";
  std::cout<<" Vector: "<<vec[0]<<", "<<vec[1]<<"
";
}

The output of this program is a little bit (at least for me) unexpected:

 Array: 1, 1
 Vector: 1, 2

That means, the temporary objects are alive during the whole initialization of the std::vector but they are created and destructed one after each other in the case of an array. I would expect the temporaries to live until the full-expression int arr[]{ID().id, ID().id}; is completed.

The standard mentions one exception concerning the lifetime of temporary objects and initialization of arrays (12.2). However I don't get its meaning and don't know why it is applied in this particular case:

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.


Overview of the results with different compilers (MSVS result is a curtesy of NathanOliver):

             Array    Vector
clang 3.8    1, 2      1, 2
g++ 6.1      1, 1      1, 2
icpc 16      1, 1      1, 2
MSVS 2015    1, 1      1, 2

As ecatmur pointed out, for aggregate initialization every element of the braced-init-list is a full-expression, thus the following code

  struct S{
      int a;
      int b;
  } s{ID().id, ID().id};
  std::cout<<" Struct: "<<s.a<<", "<<s.b<<"
";

should print Struct 1, 1 to the console. That is exactly what the program compiled by g++ does. However, clang seems to have a bug - the resulting program prints Struct 1, 2.


A bug has been reported to clang: https://llvm.org/bugs/show_bug.cgi?id=29080

question from:https://stackoverflow.com/questions/39025342/lifetime-of-temporary-objects-during-list-initialization

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
134 views
Welcome To Ask or Share your Answers For Others

1 Answer

This is core issue 1343 "Sequencing of non-class initialization", which was accepted as a Defect Report in November 2016 by paper P0570R0. The resolution proposed is part of C++17 but not therefore part of C++14, so (unless the committee decide to publish a corrigendum to C++14) this is a point of difference between C++17 and C++14.

C++14

The correct output according to the rules of the C++14 Standard is 1, 1 for the array and 1, 2 for the vector; this is because constructing a vector (including from a braced-init-list) requires a call to a constructor while constructing an array does not.

The language that governs this is in [intro.execution]:

10 - A full-expression is an expression that is not a subexpression of another expression. [...] If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. [...]

This is fine as a top-level overview, but it leaves unanswered some questions:

  • Precisely which language construct counts as the construct producing an implicit call of a function;
  • What actually counts as an implicit call of a function; presumably a call to a user-defined constructor is a call of a function, but what about a constructor that is defaulted or defined as defaulted?

An array is an aggregate so is initialized from a braced-init-list according to [dcl.init.aggr]; this says that each element is initialized directly from the corresponding element of the list, so there is no implicit function call (at least not corresponding to the overall initialization). At a syntax level, within an initializer ([dcl.init]/1) using a braced-init-list as the brace-or-equal-initializer, the full-expressions are the expressions contained within braces and separated by commas. At the end of each full-expression, the destructors of temporaries are required to run as none of the three contexts mentioned in [class.temporary] are the case here.

The case for the initialization of a vector is different, since you are using the initializer_list constructor, so an implicit call of a function (i.e. the initializer_list constructor) occurs; this means that there is an implicit full-expression surrounding the whole initialization, so the temporaries are destroyed only when the initialization of the vector completes.

Confusingly, [dcl.init.list] says that your code is "roughly equivalent" to:

const int __a[2] = {int{ID().id}, int{ID().id}};  // #1
std::vector<int> vec(std::initializer_list<int>(__a, __a + 2));

However, this has to be read in context - for example, the array backing the initializer_list has lifetime bounded by the initialization of the vector.

This was a lot clearer in C++03, which had in [intro.execution]:

13 - [Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.18). For example, in 8.5 one syntax for initializer is ( expression-list ) but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 8.5, another syntax for initializer is = initializer-clause but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ]

This paragraph is struck in its entirety from C++11; this was per the resolution to CWG 392. The resulting confusion was presumably not intended.

C++17

After P0570R0, [intro.execution] states that a full-expression is: [...]

  • an init-declarator ([dcl.decl]) [...] including the constituent expressions of the initializer, or [...]
  • an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.

So in C++17, the full-expression is arr[]{ID().id, ID().id} and vec{ID().id, ID().id} respectively, and the correct output is 1, 2 in each case, since the destruction of the first temporary ID is deferred to the end of the full-expression.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share

548k questions

547k answers

4 comments

86.3k users

...