...
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 pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage, and using the pointer as if the pointer were of type
void*
, is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:
— the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression,
— the pointer is used to access a non-static data member or call a non-static member function of the object, or
— the pointer is implicitly converted to a pointer to a virtual base class, or
— the pointer is used as the operand of astatic_cast
, except when the conversion is to pointer to cvvoid
, or to pointer to cvvoid
and subsequently to pointer to either cvchar
or cvunsigned char
, or
— the pointer is used as the operand of adynamic_cast
.
Paragraph 6 similarly describes the lifetime rules for nonpointers:
...
In this noncompliant code example, a pointer to an object is used, prior to its lifetime starting, to call a non-static nonstatic member function of the object, resulting in undefined behavior:
Code Block | ||||
---|---|---|---|---|
| ||||
struct S { void mem_fn(); }; void f() { S *s; s->mem_fn(); } |
...
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 consequently not shown.
Noncompliant Code Example
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 | ||||
---|---|---|---|---|
| ||||
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); } |
...
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 | ||||
---|---|---|---|---|
| ||||
int *g() { int i = 12; return &i; } void h(int *i); void f() { int *i = g(); h(i); } |
...
In this noncompliant code example, the function g()
returns a lambda, which captures the automatic local variable i
by reference. When that lambda is returned from the call, the reference it captured will refer to a variable whose lifetime has ended. As a result, when the lambda is executed in f()
, the use of the dangling reference in the lambda results in undefined behavior. As a general rule, functions returning lambdas should not capture by reference.
Code Block | ||||
---|---|---|---|---|
| ||||
auto g() {
int i = 12;
return [&] {
i = 100;
return i;
};
}
void f() {
int i = g()();
}
|
Compliant Solution
In this compliant solution, the lambda does not capture i
by reference but instead captures it by copy. Consequently, the lambda contains an implicit data member named i
whose lifetime is that of the lambda.
Code Block | ||||
---|---|---|---|---|
| ||||
auto g() { int i = 12; return [=] () mutable { i = 100; return i; }; } void f() { int i = g()(); } |
...
A 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-initializierinitializer. 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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <initializer_list> #include <iostream> class C { std::initializer_list<int> L; public: C() : L{1, 2, 3} {} int first() const { return *L.begin(); } }; void f() { C c; std::cout << c.first(); } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <initializer_list> #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(); } |
...