make_unique
とmake_shared
の可用性、およびunique_ptr
とshared_ptr
デストラクタによる自動削除を考えると、new
およびdelete
?
多くの場合、スマートポインターはrawポインターよりも望ましいですが、C++ 14にはnew
/delete
のユースケースがまだたくさんあります。
インプレース構築を必要とする何かを書く必要がある場合、例えば:
配置new
を使用する必要があります。場合によってはdelete
を使用する必要があります。それを回避する方法はありません。
書き込むコンテナによっては、生のポインタをストレージに使用したい場合があります。
Even標準のスマートポインタの場合、make_unique
およびmake_shared
はそれを許可しないため、カスタムの削除機能を使用する場合は、new
が必要です。
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
がほとんどすべての用途をカバーするようです。
new
およびdelete
の唯一の理由は、他の種類のスマートポインターを実装することです。
たとえば、Andrei Alexandrescuが指摘するように、パフォーマンス上の理由から共有ポインターよりも優れているため、ライブラリには、押し付けポインター:boost :: intrusive_ptrがまだありません。