web-dev-qa-db-ja.com

C ++ 17にstd :: construct_atがないのはなぜですか?

C++ 17はstd::destroy_atを追加しますが、std::construct_atに対応するものはありません。何故ですか?次のように簡単に実装できませんか?

template <typename T, typename... Args>
T* construct_at(void* addr, Args&&... args) {
  return new (addr) T(std::forward<Args>(args)...);
}

これにより、not-entirely-natural placement new構文を回避できます:

auto ptr = construct_at<int>(buf, 1);  // instead of 'auto ptr = new (buf) int(1);'
std::cout << *ptr;
std::destroy_at(ptr);
57
Daniel Langr

std::destroy_atは、直接デストラクタ呼び出しよりも2つの客観的な改善を提供します。

  1. 冗長性を削減します。

    T *ptr = new T;
    //Insert 1000 lines of code here.
    ptr->~T(); //What type was that again?
    

    もちろん、それをunique_ptrにラップして処理することを好むでしょうが、何らかの理由でそれが発生しない場合は、Tに冗長性の要素があります。型をUに変更した場合、デストラクタ呼び出しを変更する必要があります。変更しないと壊れます。 std::destroy_at(ptr)を使用すると、2つの場所で同じものを変更する必要がなくなります。

    乾燥は良いです。

  2. これにより簡単になります:

    auto ptr = allocates_an_object(...);
    //Insert code here
    ptr->~???; //What type is that again?
    

    ポインターのタイプを推測した場合、ポインターを削除するのは少し難しくなります。できませんptr->~decltype(ptr)(); C++パーサーはそのようには機能しないためです。それだけでなく、decltypeはタイプをpointerとして演deするため、演typeされたタイプからポインターの間接指定を削除する必要があります。あなたを導く:

    auto ptr = allocates_an_object(...);
    //Insert code here
    using delete_type = std::remove_pointer_t<decltype(ptr)>;
    ptr->~delete_type();
    

    そして、誰がthatを入力したいですか?

対照的に、仮想std::construct_atは、配置newに対する目的の改善を提供しません。どちらの場合も、作成するタイプを明記する必要があります。どちらの場合も、コンストラクターへのパラメーターを提供する必要があります。どちらの場合も、メモリへのポインタを提供する必要があります。

したがって、あなたの仮想std::construct_atで解決する必要はありません。

そして、それは客観的に少ない能力新しい配置より。あなたはこれを行うことができます:

auto ptr1 = new(mem1) T;
auto ptr2 = new(mem2) T{};

これらはdifferentです。最初の場合、オブジェクトはデフォルトで初期化され、初期化されないままになる場合があります。 2番目の場合、オブジェクトは値で初期化されます。

架空のstd::construct_atcannotでは、必要なものを選択できます。パラメーターを指定しない場合、デフォルトの初期化を実行するコードを持つことができますが、値の初期化用のバージョンを提供することはできません。また、パラメータなしで値を初期化できますが、デフォルトではオブジェクトを初期化できません。

40
Nicol Bolas

そのようなことはありますが、 あなたが期待するような名前ではありません

  • uninitialized_copyは、オブジェクトの範囲をメモリの初期化されていない領域にコピーします

  • uninitialized_copy_n(C++ 11)は、多数のオブジェクトをメモリの初期化されていない領域(関数テンプレート)にコピーします

  • uninitialized_fillは、範囲(関数テンプレート)で定義されたメモリの初期化されていない領域にオブジェクトをコピーします

  • uninitialized_fill_nは、オブジェクトをメモリの初期化されていない領域にコピーします。この領域は、開始とカウント(関数テンプレート)によって定義されます
  • uninitialized_move(C++ 17)は、オブジェクトの範囲をメモリの初期化されていない領域に移動します(関数テンプレート)
  • uninitialized_move_n(C++ 17)は、多数のオブジェクトをメモリの初期化されていない領域に移動します(関数テンプレート)
  • uninitialized_default_construct(C++ 17)は、範囲(関数テンプレート)で定義されたメモリの初期化されていない領域にデフォルトの初期化によってオブジェクトを構築します
  • uninitialized_default_construct_n(C++ 17)は、startおよびcount(関数テンプレート)によって定義されたメモリの初期化されていない領域にデフォルトの初期化によってオブジェクトを構築します
  • uninitialized_value_construct(C++ 17)は、範囲(関数テンプレート)で定義されたメモリの初期化されていない領域に値の初期化によってオブジェクトを構築します
  • uninitialized_value_construct_n(C++ 17)は、startとcountで定義されたメモリの初期化されていない領域に値の初期化によってオブジェクトを構築します
13
Marek R

std::allocator_traits::construct があります。以前はstd::allocatorにもう1つありましたが、それは削除されました 標準化委員会ペーパーD0174R の理論的根拠。

9
user10316011

std::construct_atがC++ 20に追加されました。そうした論文は More constexpr containers です。おそらく、これはC++ 17の新しい配置よりも十分な利点があるとは思われませんでしたが、C++ 20は状況を変えます。

この機能を追加した提案の目的は、std::vectorを含むconstexprメモリ割り当てをサポートすることです。これには、割り当てられたストレージにオブジェクトを構築する機能が必要です。ただし、void *ではなくT *の観点から、新しい取引を単純に配置するだけです。 constexpr評価には現在、未加工のストレージにアクセスする機能がないため、委員会はそれをそのまま維持することを望んでいます。ライブラリ関数std::construct_atは、型付きインターフェイスconstexpr T * construct_at(T *, Args && ...)を追加します。

これには、構築するタイプをユーザーが指定する必要がないという利点もあります。ポインターのタイプから推測されます。配置newを正しく呼び出すための構文は、一種の恐ろしく直感に反するものです。 std::construct_at(ptr, args...)::new(static_cast<void *>(ptr)) std::decay_t<decltype(*ptr)>(args...)を比較します。

6
David Stone

標準の構造関数が必要だと思います。実際、libc ++では、stl_construct.hファイルに実装の詳細として1つがあります。

namespace std{
...
  template<typename _T1, typename... _Args>
    inline void
    _Construct(_T1* __p, _Args&&... __args)
    { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
...
}

「プレースメント・ニュー」を友達にすることができるので、持っていると便利だと思います。これは、uninitialized_copyをデフォルトヒープに(たとえば、std::initializer_list要素から)必要とする移動専用タイプの優れたカスタマイズポイントです。


カスタムdetail::uninitialized_copyを使用するために(範囲の)detail::constructを再実装する独自のコンテナライブラリがあります。

namespace detail{
    template<typename T, typename... As>
    inline void construct(T* p, As&&... as){
        ::new(static_cast<void*>(p)) T(std::forward<As>(as)...);
    }
}

これは、移動のみのクラスのフレンドであると宣言され、配置newのコンテキストでのみコピーを許可します。

template<class T>
class my_move_only_class{
    my_move_only_class(my_move_only_class const&) = default;
    friend template<class TT, class...As> friend void detail::construct(TT*, As&&...);
public:
    my_move_only_class(my_move_only_class&&) = default;
    ...
};
0
alfC

constructは構文上の砂糖を提供していないようです。さらに、新しい配置よりも効率が低くなります。参照引数にバインドすると、一時的な実体化と余分な移動/コピー構築が発生します。

struct heavy{
   unsigned char[4096];
   heavy(const heavy&);
};
heavy make_heavy(); // Return a pr-value
auto loc = ::operator new(sizeof(heavy));
// Equivalently: unsigned char loc[sizeof(heavy)];

auto p = construct<heavy>(loc,make_heavy()); // The pr-value returned by
         // make_heavy is bound to the second argument,
         // and then this arugment is copied in the body of construct.

auto p2 = new(loc) auto(make_heavy()); // Heavy is directly constructed at loc
       //... and this is simpler to write!

残念ながら、関数を呼び出すときにこれらの余分なコピー/移動の構築を回避する方法はありません。転送はほぼ完全です。

一方、ライブラリのconstruct_atは、標準ライブラリの語彙を完成させることができます。

0
Oliv