Why do I need std::launder?
In the C++ language running mechanism, the compiler will build a memory model based on the logic of the source code. This memory model describes in detail the specific layout of objects in memory and their life cycle. Based on this memory model, the compiler will perform a series of optimization operations, the most common of which is to eliminate redundant memory access to improve the operation efficiency of the program.
However, when usingreinterpret_cast
Or when re-representing an object in other special ways, it may break the compiler's original memory model assumption. For example, in C++ we can useplacement new
The operator creates a new object at an existing memory location. In this case, the compiler may not be able to detect in time that the object's type has changed. If you directly access the newly created object through the old pointer at this time, the compiler operates according to the old memory model, it may lead to wrong results and even cause program crashes. The root of this error is that the behavior of the program violates the compiler's expectations, resulting in the occurrence of undefined behavior.
std::launder
The function is that it can clearly convey a message to the compiler: "I have changed the representation of the object. Please give up the assumptions made based on the representation of the old object and re-optimize according to the representation of the new object." In this way, the compiler can make reasonable optimizations based on the new situation, thereby effectively avoiding the occurrence of undefined behavior and ensuring the correctness and stability of the program.
Definition and usage of std::launder
std::launder
The definitions in the C++17 standard are as follows:
template <class T> constexpr T* launder(T* p) noexcept; // C++17 rise
As can be seen from the definition,std::launder
is a template function that accepts a type ofT*
Pointerp
As an argument, and return a same typeT*
pointer. Its specific function is to return a pointer located atp
Pointer to the object representing the address.
In usestd::launder
When it comes to this, developers need to pay close attention to the following important conditions:
-
The object must be in lifetime:
std::launder
Only used to access objects that are within a valid life cycle. If you trystd::launder
Accessing an object that has been destructed or has not been created yet will result in undefined behavior. -
Type Match: The type of the target object must be with the template parameters
T
Same, what needs to be noted here is thatstd::launder
Will ignorecv
Qualifier (const
andvolatile
qualifier). That is, no matter the object isconst
Type orvolatile
Type, as long as its actual type and template parametersT
Consistent, you can use itstd::launder
Perform processing. -
Tapping:pass
std::launder
The result pointer returned by the operation must also be accessible through the original pointer.p
touch. This means usingstd::launder
When the pointer cannot change the accessibility of the memory area pointed to. If this condition is violated,std::launder
The behavior will be undefined.
If any of the above conditions are not satisfied,std::launder
The behavior cannot be guaranteed and may cause unpredictable errors.
Typical usage scenarios
1. Handle new objects created by placement new
When we useplacement new
When creating a new object in an existing memory location, the original pointer may not be able to access the newly created object correctly. in this case,std::launder
It can play an important role to obtain a valid pointer to a new object.
Here is a specific example code:
struct X { const int n; double d; }; X* p = new X{7, 8.8}; new (p) X{42, 9.9}; // Create a new object in pint i = std::launder(p)->n; // OK, i is 42auto d = std::launder(p)->d; // OK,d yes 9.9
In the above code, first passnew
The operator creates aX
object of type and assign its pointer top
. Then, useplacement new
existp
A new memory location is createdX
Object of type. At this time, if not usedstd::launder
, directly throughp
Going to access members of a new object will result in undefined behavior. Bystd::launder(p)
To get a pointer to the new object, you can correctly access the members of the new object, ensuring that the behavior of the program is predictable.
2. Handle updates of virtual function tables
In scenarios involving virtual functions, when the type of an object changes, it may cause updates to the virtual function table (vtable). in this case,std::launder
This ensures that the new virtual function table is accessed through the correct pointer, thus avoiding undefined behavior.
Here is a specific example:
struct A { virtual int transmogrify(); }; struct B : A { int transmogrify() override { new(this) A; return 2; } }; int A::transmogrify() { new(this) B; return 1; } A i; int n = (); // Call A::transmogrify to create a B objectint m = std::launder(&i)->transmogrify(); // OK,Call B::transmogrify
In this example,A
Class andB
Classes are inheritance relationships and all define virtual functions.transmogrify
. existA::transmogrify
In the function, useplacement new
WillA
Convert object of type toB
object of type; inB::transmogrify
In the function, theB
Type object conversion backA
Object of type. Callingtransmogrify
After the function, if not usedstd::launder
, directly through&i
Calltransmogrify
Functions, since the virtual function table has changed, will cause undefined behavior. Bystd::launder(&i)
To obtain the correct pointer, you can ensure that the correct virtual function is called and the correct operation of the program.
3. In a scenario similar to std::optional
In similarstd::optional
In the implementation ofstd::launder
You can ensure that the behavior is correct when accessing a new object through a member pointer.std::optional
is a very practical type introduced in C++17, which can be used to represent a value that may or may not exist.
Here is a simplifiedstd::optional
Implementation example:
template<typename T> class optional { private: T payload; public: template<typename... Args> void emplace(Args&&... args) { payload.~T(); ::new (&payload) T(std::forward<Args>(args)...); } const T& operator*() const & { return *(std::launder(&payload)); // Use std::launder to ensure access to new objects } };
In the above code,optional
Classicemplace
Functions are used inpayload
Create a new object on the member. existoperator*
In the function,std::launder(&payload)
to get the correct pointer to the new object, thus ensuring accesspayload
The behavior of members is correct, avoiding the occurrence of undefined behavior.
Summarize
std::launder
It is a very powerful and practical tool introduced by the C++17 standard. It effectively helps developers avoid undefined behavior that may occur in complex memory operation scenarios by explicitly informing the compiler of re-representation of objects. Involvedplacement new
, virtual function table update or similarstd::optional
In the implementation of thestd::launder
All can play an important role in ensuring the correctness and stability of the procedures.
However, it should be clear thatstd::launder
Not a universal solution, it doesn't solve all pointer-related problems. Its use requires developers to be cautious when meeting specific conditions, fully understand its working principle and usage limitations.
Anyway,std::launder
As an important feature in modern C++, it is of great significance to improving the quality and reliability of C++ programs, and it deserves in-depth understanding and proficiency of every C++ developer. I hope this article can help readers understand betterstd::launder
function and usage. If readers are interested in this topic, it is recommended to read the relevant standard documents of C++17 in depth, or try to apply this feature in actual projects to deepen their understanding and mastery.
Bold style
This is all about this article about std::launder in C++17. For more related contents of C++17, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!