Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Paragraph 6 describes the lifetime rules for nonpointersnon-pointers:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
    — an lvalue-to-rvalue conversion is applied to such a glvalue,
    — the glvalue is used to access a non-static data member or call a non-static member function of the object, or
    — the glvalue is bound to a reference to a virtual base class, or
    — the glvalue is used as the operand of a dynamic_cast or as the operand of typeid.

...

Code Block
bgColor#FFcccc
langcpp
class S {
  int v;
 public:
  S() : v(12) {} // Non-trivial constructor
  void f();
 };
  
 void f() {
  // ...
  
  goto bad_idea;
  
  // ...
  S s; // Control passes over the declaration, so initialization does not take place.
  
 bad_idea:
  s.f();
 }

Compliant Solution

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();
 }

Noncompliant Code Example

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 referred to by referenced by the destination iterator has yet to start its lifetime. This is because, while space for the object has been allocated, no constructors or initializers have been invoked.

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);
}

...

A reasonable implementation of std::get_temporary_buffer() and std::copy() can result in code that behaves like the following example (with 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;
}

...