Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: editorial changes reviewed by Aaron

...

Do not use an object outside of its lifetime, except in the ways described above as being well-defined.

...

In this noncompliant code example, a pointer to an object is used to call a non-static member function of the object , prior to the beginning of the pointer's lifetime, resulting in undefined behavior:.

Code Block
bgColor#FFCCCC
langcpp
struct S {
  void mem_fn();
};
 
void f() {
  S *s;
  s->mem_fn();
}

...

In this compliant solution, storage is obtained for the pointer prior to calling S::mem_fn():.

Code Block
bgColor#ccccff
langcpp
struct S {
  void mem_fn();
};
 
void f() {
  S *s = new S;
  s->mem_fn();
  delete s;
}

An improved compliant solution would not dynamically allocate memory directly but would instead use an automatic local variable to obtain the storage and perform initialization. If a pointer were required, use of a smart pointer, such as std::unique_ptr, would be a marked improvement. However, these suggested compliant solutions would distract from the lifetime demonstration of this compliant solution and consequently are not shown.

...

In this noncompliant code example, a pointer to an object is implicitly converted to a virtual base class after the object's lifetime has ended, resulting in undefined behavior:.

Code Block
bgColor#FFCCCC
langcpp
struct B {};
 
struct D1 : virtual B {};
struct D2 : virtual B {};
 
struct S : D1, D2 {};
 
void f(const B *b) {}
 
void g() {
  S *s = new S;
  // Use s
  delete s;
 
  f(s);
}

Despite that the fact that f() never makes use of the object, its being passed as an argument to f() is sufficient to trigger undefined behavior.

...

In this compliant solution, the lifetime of s is extended to cover the call to f():.

Code Block
bgColor#ccccff
langcpp
struct B {};
 
struct D1 : virtual B {};
struct D2 : virtual B {};
 
struct S : D1, D2 {};
 
void f(const B *b) {}
 
void g() {
  S *s = new S;
  // Use s
  f(s);
 
  delete s;
}

...

In this noncompliant code example, the address of a local variable is returned from f(). When the resulting pointer is passed to h(), the lvalue-to-rvalue conversion applied to i results in undefined behavior:.

Code Block
bgColor#FFCCCC
langcpp
int *g() {
  int i = 12;
  return &i;
}
 
void h(int *i);
 
void f() {
  int *i = g();
  h(i);
}

...

In this compliant solution, the local variable returned from g() has static storage duration instead of automatic storage duration, extending its lifetime sufficiently for use within f():.

Code Block
bgColor#ccccff
langcpp
int *g() {
  static int i = 12;
  return &i;
}
 
void h(int *i);
 
void f() {
  int *i = g();
  h(i);
}

...

std::initializer_list<> object is constructed from an initializer list as though the implementation allocated a temporary array and passed it to the std::initializer_list<> constructor. This temporary array has the same lifetime as other temporary objects except that initializing a std::initializer_list<> object from the array extends the lifetime of the array exactly like binding a reference to a temporary [ISO/IEC 14882-2014].

In this noncompliant code example, a member variable of type std::initializer_list<int> is list-initialized within the constructor's ctor-initializer. Under these circumstances, the conceptual temporary array's lifetime ends once the constructor exits, and so accessing any elements of the std::initializer_list<int> member variable results in undefined behavior.

...

In this compliant solution, the std::initializer_list<int> member variable is replaced with a std::vector<int>, which copies the elements of the initializer list to the container instead of relying on a dangling reference to the temporary array:.

Code Block
bgColor#ccccff
langcpp
#include <iostream>
#include <vector>
 
class C {
  std::vector<int> l;
  
public:
  C() : l{1, 2, 3} {}
  
  int first() const { return *l.begin(); }
};
 
void f() {
  C c;
  std::cout << c.first();
}

...

This compliant solution ensures that s is properly initialized prior to performing the local jump:.

Code Block
bgColor#ccccff
langcpp
class S {
  int v;
public:
  S() : v(12) {} // Non-trivial constructor
  void f();
};
 
void f() {
  S s;

  // ...

  goto bad_idea;

  // ...

bad_idea:
  s.f();
}

...

In this noncompliant code example, f() is called with an iterable range of objects of type S. These objects are copied into a temporary buffer using std::copy(), and when processing of those objects is complete, the temporary buffer is deallocated. However, the buffer returned by std::get_temporary_buffer() does not contain initialized objects of type S, so when std::copy() dereferences the destination iterator, it results in undefined behavior because the object being assigned into referred to by the destination iterator has yet to start its lifetime.

Code Block
bgColor#FFcccc
langcpp
#include <algorithm>
#include <cstddef>
#include <memory>
#include <type_traits>
 
class S {
  int i;

public:
  S() : i(0) {}
  S(int i) : i(i) {}
  S(const S&) = default;
  S& operator=(const S&) = default;
};

template <typename Iter>
void f(Iter i, Iter e) {
  static_assert(std::is_same<typename std::iterator_traits<Iter>::value_type, S>::value,
                "Expecting iterators over type S");
  ptrdiff_t count = std::distance(i, e);
  if (!count) {
    return;
  }
  
  // Get some temporary memory.
  auto p = std::get_temporary_buffer<S>(count);
  if (p.second < count) {
    // Handle error; memory wasn't allocated, or insufficient memory was allocated.
    return;
  }
  S *vals = p.first; 
  
  // Copy the values into the memory.
  std::copy(i, e, vals);
  
  // ...
  
  // Return the temporary memory.
  std::return_temporary_buffer(vals);
}

Implementation Details

A reasonable implementation of std::get_temporary_buffer() and std::copy() can result in code that behaves like the following example (error-checking elided):

Code Block
unsigned char *buffer = new (std::nothrow) unsigned char[sizeof(S) * object_count];
S *result = reinterpret_cast<S *>(buffer);
while (i != e) {
  *result = *i; // Undefined behavior
  ++result;
  ++i;
}

...

This compliant solution uses std::copy() with a std::raw_storage_iterator as the destination iterator , with the same well-defined results as using std::uninitialized_copy(). As with the previous compliant solution, identical code from the noncompliant code example has been elided for brevity.

...