ライブラリの基礎技術仕様V2の構成 std::observer_ptr
の正確なポイントは何ですか?
動的なメモリの安全性を追加しない場合、余分な手順のように見えますが、T*
をラップするだけです。
すべてのコードで std::unique_ptr
を使用して、オブジェクトの明示的な所有権を取得する必要があります std::shared_ptr
ここで、オブジェクトの所有権を共有できます。
これは非常にうまく機能し、すでに破壊されたオブジェクトの誤った逆参照を防ぎます。
std::observer_ptr
は、もちろん、観測されたオブジェクトの存続期間について保証しません。
std::unique_ptr
またはstd::shared_ptr
から構築する場合、そのような構造での使用が見られますが、単にT*
を使用しているすべてのコードは、おそらくそれを継続し、彼らが何かに移動することを計画している場合、それはstd::shared_ptr
やstd::unique_ptr
(使用方法によって異なります)になります。
簡単な例の関数を考えます:
template<typename T>
auto func(std::observer_ptr<T> ptr){}
スマートポインターが監視されている間に、格納されているオブジェクトを破壊しないようにすると便利です。
しかし、std::shared_ptr
またはstd::unique_ptr
を観察したい場合は、次のように記述する必要があります。
auto main() -> int{
auto uptr = std::make_unique<int>(5);
auto sptr = std::make_shared<int>(6);
func(uptr.get());
func(sptr.get());
}
それはそれより安全ではありません:
template<typename T>
auto func(T *ptr){}
では、この新しい構造の用途は何ですか?
それは自己文書化ソースのためだけですか?
proposal は、自己文書化のためだけのものであることを明確にします。
この論文は
observer_ptr
、(あまりではない)スマートポインタ型で、その指示先、つまり監視対象のオブジェクトに対する所有権の責任を持ちません。そのため、これは、生のポインター型のほぼドロップインの代替として意図されており、ボキャブラリー型として、コードリーダーによる詳細な分析を必要とせずにその意図された使用法を示すという利点があります。
共有アクセスが必要であるが共有所有権が必要でない場合
問題は、rawポインターが依然として非常に有用であり、完全に立派なユースケースシナリオがあることです。
rawポインタがスマートポインタによって管理されている場合、クリーンアップが保証されているため、スマートポインタ、生のポインタを通じて実際のデータにアクセスすることは理にかなっていますスマートポインタが管理しています。
したがって、通常は生のポインタをとる関数を作成する場合、関数がそのポインタを削除しないことを保証する良い方法は、std::observer_ptr
のような強く型付けされたクラスを使用することです。
マネージド生のポインターをstd::observer_ptr
関数パラメーターの引数として渡すと、関数がdelete
に渡らないことがわかります。
これは、関数が「ポインタを指定してください。割り当てに干渉することはありません。観察するためだけに使用します」と言う方法です。
ちなみにstd::observer_ptr
という名前にはあまり興味がありません。しかし、それは本当ではありません。もっとaccess_ptr
のようなものを使っていただろう。
追記:
これは、std::shared_ptr
とは異なる使用例です。 std::shared_ptr
は、共有所有権に関するものであり、どの所有オブジェクトを判別できない場合はonlyを使用する必要があります最初に範囲外になります。
一方、std::observer_ptr
は、accessを共有したいがownershipを共有したくない場合に使用します。
accessを共有するためだけにstd::shared_ptr
を使用することは、非常に非効率的である可能性があるため、実際には適切ではありません。
したがって、std::unique_ptr
またはstd::shared_ptr
を使用してターゲットポインタを管理している場合でも、の使用例はまだありますraw-pointers、したがってstd::observer_ptr
の有理数。
はい。
提案 から、std::observer_ptr
は、ポインタがではなく(= /// =)非所有参照であることを文書化するためのものであるようです所有リファレンス、array、stringまたはiterator。
ただし、observer_ptr<T>
ではなくT*
を使用することには、他にもいくつかの利点があります。
observer_ptr
は常にnullptr
に初期化されます。通常のポインタは、コンテキストに応じて初期化される場合とされない場合があります。observer_ptr
は、参照に対して意味のある演算のみをサポートします;これにより、正しい使用法が適用されます。operator[]
はarray操作であるため、observer_ptr
には実装されていません。observer_ptr
ではポインタ演算はできません。observer_ptr
sには、すべての実装で 厳密な弱い順序付け がありますが、これは2つの任意のポインターに対して保証されていません。これは、operator<
がstd::less
の observer_ptr
として実装されているためです( std::unique_ptr
および std::shared_ptr
と同様に / )。observer_ptr<void>
はサポートされていないようです。そのため、より安全なソリューションの使用が促進される可能性があります(例: std::any
および std::variant
)生のポインタに対してstd::observer_ptr
を使用することの1つの素晴らしい結果は、Cから継承された、混乱しやすくエラーが発生しやすい複数のポインタのインスタンス化構文のより良い代替手段を提供することです。
std::observer_ptr<int> a, b, c;
の改善です
int *a, *b, *c;
これはC++の観点からは少し奇妙であり、次のように簡単にタイプミスすることができます
int* a, b, c;
はい、_std::observer_ptr
_のポイントは、主に単なる「自己文書化」であり、それ自体が有効な目的です。しかし、「オブザーバー」ポインターが正確に何であるかが明確でないため、間違いなくそれはそれの素晴らしい仕事をしないと指摘されるべきです。まず、Galikが指摘するように、一部の名前はターゲットを変更しないという約束を意味しているようであり、これは意図ではないため、_access_ptr
_のような名前の方が適切です。 2番目に、修飾子がない場合、名前はその「非機能」動作の推奨を意味します。たとえば、_std::weak_ptr
_を「オブザーバー」ポインターのタイプと見なすことができます。ただし、_std::weak_ptr
_は、(割り当て解除された)オブジェクトに安全に失敗するようにアクセスできるメカニズムを提供することにより、ポインターがターゲットオブジェクトよりも長く存続するケースに対応しています。 _std::observer_ptr
_の実装はこのケースに対応していません。したがって、おそらく_raw_access_ptr
_は、その機能上の欠点をより適切に示すため、より良い名前になります。
それで、あなたが正当に尋ねるように、この機能的に挑戦された「非所有」ポインタの意味は何ですか?主な理由はおそらくパフォーマンスです。多くのC++プログラマーは、_std::share_ptr
_のオーバーヘッドが高すぎると認識しているため、「オブザーバー」ポインターが必要な場合はそのままのポインターを使用します。提案されている_std::observer_ptr
_は、許容可能なパフォーマンスコストでコードの明瞭度を少し改善しようとします。特に、パフォーマンスコストはゼロです。
残念ながら、普及しているようですが、私の意見では、生のポインターを「オブザーバー」ポインターとして使用することの安全性についての非現実的な楽観論です。特に、ターゲットオブジェクトが_std::observer_ptr
_よりも長く存続しなければならないという要件を述べるのは簡単ですが、それが満たされていることを完全に確実にすることは必ずしも簡単ではありません。この例を考えてみましょう:
_struct employee_t {
employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
std::string m_first_name;
std::string m_last_name;
};
void replace_last_employee_with(const std::observer_ptr<employee_t> p_new_employee, std::list<employee_t>& employee_list) {
if (1 <= employee_list.size()) {
employee_list.pop_back();
}
employee_list.Push_back(*p_new_employee);
}
void main(int argc, char* argv[]) {
std::list<employee_t> current_employee_list;
current_employee_list.Push_back(employee_t("Julie", "Jones"));
current_employee_list.Push_back(employee_t("John", "Smith"));
std::observer_ptr<employee_t> p_person_who_convinces_boss_to_rehire_him(&(current_employee_list.back()));
replace_last_employee_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}
_
replace_last_employee_with()
関数の作成者が、新入社員への参照が、置き換えられる既存の従業員への参照である可能性があることは決してなかったかもしれません。 _std::observer_ptr<employee_t>
_パラメータは、使用が完了する前に割り当て解除されます。
これは不自然な例ですが、この種のことは、より複雑な状況で簡単に発生する可能性があります。もちろん、ほとんどの場合、生のポインタを使用しても完全に安全です。問題は、実際には安全ではないのに安全であると簡単に想定できる少数のケースがあることです。
_std::observer_ptr<employee_t>
_パラメータを_std::shared_ptr
_または_std::weak_ptr
_で置き換えることが受け入れられない理由である場合、別の安全なオプションがあります-これは答えの恥知らずなプラグ部分です-"- 登録済みポインタ "。 「登録済みポインタ」は、生のポインタと同じように動作するスマートポインタですが、ターゲットオブジェクトが破棄されると(自動的に)_null_ptr
_に設定されます。デフォルトでは、オブジェクトにアクセスしようとすると例外がスローされますすでに削除されています。それらは一般的にはstd :: shared_ptrsより 速い ですが、パフォーマンス要求が本当に厳しい場合、コンパイル時のディレクティブを使用して、登録されたポインターを「無効」にして(自動的に対応する生のポインターに置き換える)、デバッグ/テスト/ベータモードでのみ使用され、オーバーヘッドが発生します。
したがって、生のポインタに基づく「オブザーバ」ポインタが存在する場合、おそらく登録済みのポインタに基づくものと、おそらくOPが示唆するように、std :: shared_ptrに基づくものもあるはずです。
ドキュメントの使用例とは別に、オブザーバーの装飾なしで生のポインターを渡すときに発生する可能性のある現実の問題があります。他のコードは、生のポインタに対して生涯の責任を誤って引き受け、std::unique_ptr
、std::shared_ptr
、またはdelete
によるオブジェクトの単純な破棄。
これは、所有権ルールが完全に確立されていない場合にアップグレードされる可能性があるレガシーコードに特に当てはまります。 observer_ptr
は、オブジェクトのライフタイムを転送できないというルールを適用するのに役立ちます。
次の例を考えてみます。
#include <iostream>
#include <memory>
struct MyObject
{
int value{ 42 };
};
template<typename T>
void handlerForMyObj(T ptr) noexcept
{
if (42 != ptr->value) {
// This object has gone rogue. Dispose of it!
std::cout << "The value must be 42 but it's actually " << ptr->value << "!\n";
delete ptr;
return;
}
std::cout << "The value is " << ptr->value << ".\n";
}
void func1()
{
MyObject myObj;
MyObject *myObjPtr = &myObj;
myObj.value = 24;
// What?! Likely run-time crash. BOO!
handlerForMyObj(myObjPtr);
}
void func2()
{
MyObject myObj;
std::observer_ptr<MyObject> myObjObserver{ &myObj };
myObj.value = 24;
// Nice! Compiler enforced error because ownership transfer is denied!
handlerForMyObj(myObjObserver);
}
int main(int argn, char *argv[])
{
func1();
func2();
}
生のポインタの場合、オブジェクトの誤った削除は実行時にのみ検出される可能性があります。しかし、observer_ptr
の場合、delete
演算子はオブザーバーに適用できません。