このページはC++17に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
概要
placement new
を使用して、参照型やconst
メンバ変数を含む構造体/クラスを置き換える際、オブジェクト生存期間(lifetime)に基づいた最適化の抑止をコンパイラに伝える関数std::launder()
を用いることで、未定義動作を引き起こすような文脈で参照型やconst
メンバ変数へのアクセスができる。
仕様
n4659 [ptr.launder]/5より
struct X { const int n; };
X *p = new X{3};
const int a = p->n;
new (p) X{5}; // X::nはconstなので、pは新しいオブジェクトを指していない
const int b = p->n; // 未定義動作
const int c = std::launder(p)->n; // OK
例
#include <iostream>
#include <new>
struct X
{
int& n;
};
int main()
{
int n = 12;
X *p = new X{n};
int m = 34;
new (p) X{m};
n = 56;
m = 78;
// const int a = p->n; // 未定義動作
const int a = std::launder(p)->n; // OK
std::cout << a << std::endl;
}
出力
78
この機能が必要になった背景・経緯
以前は、placement new
の戻り値を用いることで未定義動作を起こさないようにすることができた。そして、std::optional
のようなクラスでは、次のようにplacement new
の戻り値を保持するために、メンバにポインタを追加する必要があった。
template <typename T>
class coreoptional
{
private:
T payload;
T* p; // placement newの戻り値を使えるようにする
public:
coreoptional(const T& t)
: payload(t) {
p = &payload;
}
template<typename... Args>
void emplace(Args&&... args) {
payload.~T();
p = ::new (&payload) T(std::forward<Args>(args)...);
}
const T& operator*() const & {
return *p; // ここで payload を使わないでください!
}
};
このオーバーヘッドを避けるためにstd::launder()
関数が導入された。
備考
ストレージの再利用については、ほかにも追加された機能がある。ストレージを再利用する際は、Lifetime - cppreference.comを参照してもよいと思われる。
参照
- std::launder関数 - yohhoyの日記
- [C++]メンバに参照型を持つクラス(構造体)の取り扱い - 地面を見下ろす少年の足蹴にされる私
- P0532R0 On
launder()
- Core Issue 1776: Replacement of class objects containing reference members
- std::launder - cppreference.com
- Pointer safety and placement new
- Implementability of std::optional (std :: optionalの実装可能性)
- Lifetime - cppreference.com