Temporary lifetime extension in C++11
(JP 01/05/2018 originally presented during a live coding of the supporting code using catch2)
Temproraries are often created when returning from a function. A temprorary lifetime ends when the expression ends: CPP Reference: Lifetime
Since C++11, when assigning a temporary to a const reference, its lifetime will be extended to be the lifetime of the reference. This can be great to avoid copy, but can lead to dangling references:
- Assigning to const reference is safe if the assigned value is:
- A temporary.
- A reference to an object, or reference to a member of an object whose lifetime will outlive the new reference.
- Assigning to const reference is unsafe if the assigned value is:
- A reference to a temporary object, or reference to a member of a temporary object whose lifetime will not outlive the reference.
Alternatively, these can be used as paramter to functions:
- Because any temporary’s lifetime will ends after the function is called (end of the expression), chaining functions will be safe whether they return reference or temproraries.
- It can be dangerous though as a trivial but incorrect refactoring will make this code unsafe (i.e a reference to temporary is assigned to a named const reference).
- The safe refactoring would be to assign to a const value even if this create a possible copy.
Examples of problematic code:
- const auto& ref = GetCollection().at(0);
- const auto* ptr = GetString().c_str();
// cpp11_vs2017.cpp : Defines the entry point for the console application.
//
#include "catch.hpp"
#include <map>
#include <string>
struct LifeTime;
struct Valid
{
int m_valid = 0;
int m_constructed = 0;
};
std::ostream& operator<<( std::ostream& os, const Valid& value )
{
return os << "Valid{" << value.m_valid << "," << value.m_constructed << "}";
}
bool operator==( const Valid& v1, const Valid& v2 )
{
return v1.m_valid == v2.m_valid && v1.m_constructed == v2.m_constructed;
}
static const int Destroyed = 0;
static const int Constructed = 1;
static std::map< const LifeTime*, Valid > s_live;
struct Fixture
{
Fixture()
{
s_live.clear();
}
};
struct LifeTime
{
LifeTime()
{
s_live[ this ].m_valid++;
s_live[ this ].m_constructed++;
}
~LifeTime()
{
s_live[ this ].m_valid--;
}
LifeTime( const LifeTime& t ) : LifeTime()
{
}
LifeTime& operator=( const LifeTime& t )
{
}
Valid Check() const
{
return s_live[ this ];
}
};
struct StructTest1
{
LifeTime Get() const
{
return m_value;
};
const LifeTime& GetCRef() const
{
return m_value;
};
LifeTime m_value;
};
LifeTime GetLifeTime()
{
char a[ 20 ]; // add padding to avoid same address
(void)a;
return LifeTime{};
}
StructTest1 GetStructTest1()
{
char a[ 20 ]; // add padding to avoid same address
(void)a;
return StructTest1{};
}
void Check( const Valid& actual, const Valid& expected, int numLive )
{
CHECK( actual == expected );
CHECK( s_live.size() == numLive );
}
TEST_CASE_METHOD( Fixture, "LifeTime", "[test]" )
{
LifeTime t1;
const auto& ref = t1;
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 1 );
}
TEST_CASE_METHOD( Fixture, "From Struct ref", "[test]" )
{
StructTest1 s1;
const auto& ref = s1.GetCRef();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 1 );
}
TEST_CASE_METHOD( Fixture, "From Struct copy", "[test]" )
{
StructTest1 s1;
const auto& ref = s1.Get();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 2 );
}
TEST_CASE_METHOD( Fixture, "temporary LifeTime", "[test]" )
{
const auto& ref = LifeTime{};
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 1 );
}
TEST_CASE_METHOD( Fixture, "temporary From Struct ref", "[test]" )
{
const auto& ref = StructTest1{}.GetCRef();
CHECK( ref.Check() == Valid{ Destroyed, 1 } ); // use after free
CHECK( s_live.size() == 1 );
}
TEST_CASE_METHOD( Fixture, "temporary From Struct copy", "[test]" )
{
const auto& ref = StructTest1{}.Get();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 2 );
}
TEST_CASE_METHOD( Fixture, "temporary From Struct bound ref", "[test]" )
{
const auto& s1 = StructTest1{};
const auto& ref = s1.GetCRef();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 2 );
}
TEST_CASE_METHOD( Fixture, "temporary From Struct bound copy", "[test]" )
{
const auto& s1 = StructTest1{};
const auto& ref = s1.Get();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 3 );
}
TEST_CASE_METHOD( Fixture, "returned temporary LifeTime", "[test]" )
{
const auto& ref = GetLifeTime();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 1 );
}
TEST_CASE_METHOD( Fixture, "returned temporary From Struct ref", "[test]" )
{
const auto& ref = GetStructTest1().GetCRef();
CHECK( ref.Check() == Valid{ Destroyed, 1 } ); // use after free
CHECK( s_live.size() == 2 );
}
TEST_CASE_METHOD( Fixture, "returned temporary From Struct copy", "[test]" )
{
const auto& ref = GetStructTest1().Get();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 3 );
}
TEST_CASE_METHOD(
Fixture,
"returned temporary From Struct bound ref",
"[test]" )
{
const auto& s1 = GetStructTest1();
const auto& ref = s1.GetCRef();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 2 );
}
TEST_CASE_METHOD(
Fixture,
"returned temporary From Struct bound copy",
"[test]" )
{
const auto& s1 = GetStructTest1();
const auto& ref = s1.Get();
CHECK( ref.Check() == Valid{ Constructed, 1 } );
CHECK( s_live.size() == 3 );
}
TEST_CASE_METHOD(
Fixture,
"In Expression returned temporary LifeTime",
"[test]" )
{
Check( GetLifeTime().Check(), Valid{ Constructed, 1 }, 1 );
}
TEST_CASE_METHOD(
Fixture,
"In Expression returned temporary From Struct ref",
"[test]" )
{
Check( GetStructTest1().GetCRef().Check(), Valid{ Constructed, 1 }, 2 );
}
TEST_CASE_METHOD(
Fixture,
"In Expression returned temporary From Struct copy",
"[test]" )
{
Check( GetStructTest1().Get().Check(), Valid{ Constructed, 1 }, 3 );
}
Presented at IndigoVision during a training session on 01/05/2018.
This work by Jean-Philippe DUFRAIGNE is licensed under a Creative Commons Attribution 4.0 International License.