最終更新日時(UTC):
が更新

履歴 編集

参照メンバをもつクラスの置き換え(C++17)

概要

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を参照してもよいと思われる。

参照