Lifetime of C++ temporaries (C++ only)

The C++ Language Standard describes the lifetime of temporaries in section Temporary Object [class.temporary]. When you are porting an application from a compiler that implements late temporary destruction, you might need to extend the lifetime of C++ temporaries beyond which is specified in the C++ Language Standard. In this way, you can closely replicate the nonstandard compliant behavior of your previous compiler.

It is possible that a program incorrectly depends on resources, which might have been previously released during destruction of a temporary. See Example 1. In such cases, a compiler that incorrectly destroys a temporary later than it should be, might execute the resulting program in the wanted way. Such problems might surface during porting, when correct insertion of temporary destructors yields invalid access to a released resource.

With IBM® XL C/C++ compilers, you can extend the lifetime of temporaries to reduce migration difficulty. This is enabled by specifying option LANGLVL(TEMPSASLOCALS). When enabled, the lifetime of temporaries is extended as though such temporaries are treated as local variables declared in the inner-most containing lexical scope. Most temporaries will be destroyed when their enclosing scope is exited, rather than when the enclosing full-expression is completed. See Example 2.

Temporaries constructed in the condition statement of an if-statement should be destroyed at the end of execution of the if-statement. Temporary destruction is delayed until after any else-if or else blocks, as would a variable declared in the condition statement.

Temporaries constructed in the condition of a switch statement should be destroyed at the end of execution of the switch statement.

Temporaries for which the inner-most enclosing lexical scope is the lexical scope of a switch statement should be handled in the standard compliant way.

Temporaries constructed in the condition or increment expressions of a loop must be destroyed in the standard compliant way.

When LANGLVL(ANSIFOR) is in effect, temporaries constructed in the for-init statement must be destroyed at the end of execution of the for loop. When LANGLVL(NOANSIFOR) is in effect, temporaries constructed in the for-init statement must be destroyed at the end of execution of the inner-most lexical block containing the for-loop.

Temporaries constructed at namespace scope must be handled in the standard compliant way. See Example 3.

When INFO(POR) is in effect, and the lifetime of a temporary would otherwise be extended by this feature, and the inner-most containing lexical scope of the temporary contains a label definition that follows the construction of a temporary, that temporary shall be handled in the standard compliant way. See Example 4.

When INFO(POR) is in effect, and the lifetime of a temporary would otherwise be extended by this feature, and the inner-most containing lexical scope of the temporary contains a computed goto that follows the construction of a temporary, that temporary shall be handled in the standard compliant way. See Example 5.

Examples

Example 1

>cat myString.h 
#include <string>
#define MY_SL_STD(MY_NAME) ::std::MY_NAME

class MYString
{
public:
  // common constructors
  MYString()                                                    {}
  MYString(const MY_SL_STD(string&) data)  : data_(data)        {}
  MYString(const MYString& str)            : data_(str.data_)   {}
  MYString(char c, size_t N)               : data_(N, c)        {}
  MYString(const char* s)                  : data_(s)           {}
  MYString(const char* s, size_t N)        : data_(s,N)         {}
  
  // constructor explicitly from char
  MYString(char c)                         : data_(1,c)         {}

  ~MYString() {}
 const char*       data() const { return data_.c_str(); }

  // Type conversion:
 operator const char*() const { return data_.c_str(); }

protected:
   MY_SL_STD(string) data_;
};

>cat myString.C
#include <iostream.h>
#include "mystring.h"

class A 
{ 
public:
  A(const char * str_)
  {
    strcpy(str, str_);
  }
  MYString getStr()
  {
     return str;
  }
  void print()
  {
    cout<<"object A "<< str <<endl;
  }

private:     
  char str[2000]; 
};  

void foo(const char* s) 
{
  cout<<"foo: "<< s <<endl;
}

int main() 
{     
  A a("This is a test");     
  a.print();     
  const char * p = (const char*) a.getStr(); 
  cout <<"p= " << p <<endl;
  return (0);
}
>xlC myString.C 
>a.out 
object A This is a test 
p=  e@ 

In this example, the char array in the object is converted to a MYString to pass it out of getStr. The method A::getStr would change to A::MYString object. Whenever the getStr is used, the program will generate incorrect results, thus indicating a problem.

When the call to a getStr() is made, the method takes the str buffer and constructs a MYString object. It is a MYString object that is returned, and the code calls a MYString method to convert to a const char *. This method returns a pointer to a temporary copy of the string, "This is a test." Once the pointer has been received and stored into the p variable, the destructor for the temporary object is called. This is where the memory referenced by p now no longer contains "This is a test." Since the character string is a temporary copy, the string doesn't exist anywhere except in the original location inside the object. Any use of the p will access garbage.

Because the creation of these temporary objects uses dynamic memory, you cannot really depend upon the object still being valid and containing the same contents any longer than the statement in which it is used.

Example 2

#include<cstdio>
struct S {   
  S() { printf("S::S() ctor at 0x%lx.\n", this); }   
  S(const S& from) { printf("S::S(const S&) copy ctor at 0x%lx.\n", this); }   
  ~S() { printf("S::~S() dtor at 0x%lx.\n", this); } 
} s1; 
void foo(S s) { } 
int main() {   
  foo(s1);   
  printf("hello world.\n");   
  return 0; 
}
The C++ Standard compliant output of this program is:
 S::S() ctor at 0x20000d7c. 
 S::S(const S&) copy ctor at 0x2ff221e0. 
 S::~S() dtor at 0x2ff221e0. 
 hello world. 
 S::~S() dtor at 0x20000d7c.
Note that the temporary copy constructed for the call to foo is destroyed upon return from foo. When the lifetime of the temporary is extended, the output of this program shall be:
 S::S() ctor at 0x20000d7c. 
 S::S(const S&) copy ctor at 0x2ff221e0. 
 hello world. 
 S::~S() dtor at 0x2ff221e0. 
 S::~S() dtor at 0x20000d7c.

The temporary copy constructed for the call to foo is now destroyed when the enclosing scope is exited. It is therefore destroyed after the print of "hello world."

Example 3

struct S {   
  S(int);   
  ~S();   
  int i; 
} s1(42); 
  
int bar(S s); 
  
int gi = bar(s1); //the temporary for argument s of bar is not affected
                  //because it is constructed during static initialization. 

This example lists hardcoded addresses, which vary with the system the program is running on.

Example 4

struct S {   
  S(int);   
  ~S();   
  int i; 
}; 
  
void bar(S s); 
  
int main() {   
  S s1(42);   
  bar(s1);   // the temporary for argument s of bar is not affected
             // because of the label definition that follows. 
bypass:   
  s1.i = 42; // s1 should be referenced after call to bar or a temporary may not
             // be constructed.
  return 0;  // the temporary would otherwise be destroyed here. 
} 

Example 5

struct S {   
  ~S(); 
} s1; 
  
void bar(S s); 
  
void foo(void *p) {   
bar(s1);   // the temporary for argument s of bar is not affected
           // because of the computed goto that follows.
goto *p; 

}