web-dev-qa-db-ja.com

新規および削除はC ++ 14でもまだ有用ですか?

make_uniquemake_sharedの可用性、およびunique_ptrshared_ptrデストラクタによる自動削除を考えると、newおよびdelete

60
Michael

多くの場合、スマートポインターはrawポインターよりも望ましいですが、C++ 14にはnew/deleteのユースケースがまだたくさんあります。

インプレース構築を必要とする何かを書く必要がある場合、例えば:

  • メモリプール
  • アロケータ
  • タグ付きバリアント
  • バッファへのバイナリメッセージ

配置newを使用する必要があります。場合によってはdeleteを使用する必要があります。それを回避する方法はありません。

書き込むコンテナによっては、生のポインタをストレージに使用したい場合があります。

Even標準のスマートポインタの場合、make_uniqueおよびmake_sharedはそれを許可しないため、カスタムの削除機能を使用する場合は、newが必要です。

36
Barry

newをそのまま呼び出すのではなく、_make_unique_および_make_shared_を使用するのが比較的一般的な選択です。ただし、必須ではありません。その規則に従うことを選択した場合、newを使用する場所がいくつかあります。

まず、非カスタムプレースメントnew(「非カスタム」部分は無視し、単にプレースメントnewと呼びます)は、標準(非プレースメント)newとはまったく異なるカードゲームです。これは、手動でデストラクタを呼び出すことと論理的にペアになります。標準のnewは、フリーストアからリソースを取得し、その中にオブジェクトを構築します。これは、deleteとペアになっており、オブジェクトを破棄してストレージをフリーストアにリサイクルします。ある意味では、標準のnewは内部で配置newを呼び出し、標準のdeleteは内部でデストラクタを呼び出します。

配置newは、一部のストレージでコンストラクターを直接呼び出す方法であり、高度なライフタイム管理コードに必要です。 optional、アラインメントされたストレージにタイプセーフなunion、またはスマートポインタ(統合ストレージと非ユニファイドライフタイム、_make_shared_など)を実装する場合は、配置newを使用します。次に、特定のオブジェクトの有効期間の終わりに、そのデストラクタを直接呼び出します。プレースメント以外のnewおよびdeleteと同様に、プレースメントnewと手動デストラクタ呼び出しはペアで行われます。

カスタム配置newは、newを使用するもう1つの理由です。カスタム配置newを使用して、非グローバルプールからリソースを割り当てることができます-スコープ割り当て、またはプロセス間共有メモリページへの割り当て、ビデオカード共有メモリへの割り当てなど-およびその他の目的。カスタム配置newを使用してメモリを割り当てる_make_unique_from_custom_を作成する場合は、newキーワードを使用する必要があります。カスタムプレースメントnewは、新しいプレースメントのように機能する(実際にはリソースを取得せず、リソースが何らかの形で渡される)か、標準のnewのように機能する(リソースを取得する、つまり渡された引数を使用する) 。

カスタムプレースメントdeleteは、カスタムプレースメントnewがスローした場合に呼び出されるため、ユーザーが作成する必要がある場合があります。 C++では、カスタム配置deleteを呼び出さず、 (C++) あなたに電話する(r過負荷)

最後に、_make_shared_および_make_unique_は、カスタムの削除機能をサポートしていないという点で不完全な関数です。

_make_unique_with_deleter_を作成している場合でも、_make_unique_を使用してデータを割り当てることができ、.release()を使用して、deleted-with-deleterを管理できます。削除者がその状態を_unique_ptr_または別の割り当てではなく、ポイントされたバッファーに詰めたい場合は、ここでnewの配置を使用する必要があります。

_make_shared_の場合、クライアントコードは「参照カウントスタブ」作成コードにアクセスできません。私が知る限り、「オブジェクトと参照カウントブロックの割り当ての組み合わせ」とカスタム削除機能の両方を簡単に使用することはできません。

さらに、_make_shared_は、_weak_ptr_ sが持続する限り、オブジェクト自体のリソース割り当て(ストレージ)を持続させます。場合によっては、これが望ましくない場合があるので、それを避けるためのshared_ptr<T>(new T(...))

プレースメント以外のnewを呼び出す必要があるいくつかのケースでは、_make_unique_を呼び出し、その_unique_ptr_とは別に管理する場合は、.release()ポインターを呼び出すことができます。これにより、リソースのRAIIカバレッジが増加し、例外やその他の論理エラーがある場合に、リークが発生する可能性が低くなります。


上記のように、単一の割り当てブロックを簡単に使用する共有ポインターでカスタム削除機能を使用する方法がわかりませんでした。これを巧妙に行う方法のスケッチを以下に示します。

_template<class T, class D>
struct custom_delete {
  std::Tuple<
    std::aligned_storage< sizeof(T), alignof(T) >,
    D,
    bool
  > data;
  bool bCreated() const { return std::get<2>(data); }
  void markAsCreated() { std::get<2>()=true; }
  D&& d()&& { return std::get<1>(std::move(data)); }
  void* buff() { return &std::get<0>(data); }
  T* t() { return static_cast<T*>(static_cast<void*>(buff())); }
  template<class...Ts>
  explicit custom_delete(Ts...&&ts):data(
    {},D(std::forward<Ts>(ts)...),false
  ){}
  custom_delete(custom_delete&&)=default;
  ~custom_delete() {
    if (bCreated())
      std::move(*this).d()(t());
  }
};

template<class T, class D, class...Ts, class dD=std::decay_t<D>>
std::shared_ptr<T> make_shared_with_deleter(
  D&& d,
  Ts&&... ts
) {
  auto internal = std::make_shared<custom_delete<T, dD>>(std::forward<D>(d));
  if (!internal) return {};
  T* r = new(internal->data.buff()) T(std::forward<Ts>(ts...));
  internal->markAsCreated();
  return { internal, r };
}
_

私はそれでうまくいくと思います。私は、Tupleを使用して、ステートレス削除機能がアップスペースを使用しないようにしようとしましたが、失敗した可能性があります。

ライブラリ品質のソリューションでは、T::T(Ts...)noexceptである場合、bCreatedが構築される前に_custom_delete_を破棄する必要がないため、Tオーバーヘッドを削除できます。

私が考えられる唯一の理由は、unique_ptrまたはshared_ptrでカスタムの削除機能を使用したい場合があることです。カスタム削除機能を使用するには、newの結果を渡してスマートポインターを直接作成する必要があります。これは頻繁ではありませんが、実際には発生します。

それ以外はmake_shared/make_uniqueがほとんどすべての用途をカバーするようです。

3
Mark B

newおよびdeleteの唯一の理由は、他の種類のスマートポインターを実装することです。

たとえば、Andrei Alexandrescuが指摘するように、パフォーマンス上の理由から共有ポインターよりも優れているため、ライブラリには、押し付けポインター:boost :: intrusive_ptrがまだありません。

1
TheCppZoo