web-dev-qa-db-ja.com

observer_ptrの使用

ライブラリの基礎技術仕様V2の構成 std::observer_ptr の正確なポイントは何ですか?

動的なメモリの安全性を追加しない場合、余分な手順のように見えますが、T*をラップするだけです。

すべてのコードで std::unique_ptr を使用して、オブジェクトの明示的な所有権を取得する必要があります std::shared_ptr ここで、オブジェクトの所有権を共有できます。

これは非常にうまく機能し、すでに破壊されたオブジェクトの誤った逆参照を防ぎます。

std::observer_ptrは、もちろん、観測されたオブジェクトの存続期間について保証しません。

std::unique_ptrまたはstd::shared_ptrから構築する場合、そのような構造での使用が見られますが、単にT*を使用しているすべてのコードは、おそらくそれを継続し、彼らが何かに移動することを計画している場合、それはstd::shared_ptrstd::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){}

では、この新しい構造の用途は何ですか?

それは自己文書化ソースのためだけですか?

43
CoffeeandCode

proposal は、自己文書化のためだけのものであることを明確にします。

この論文はobserver_ptr、(あまりではない)スマートポインタ型で、その指示先、つまり監視対象のオブジェクトに対する所有権の責任を持ちません。そのため、これは、生のポインター型のほぼドロップインの代替として意図されており、ボキャブラリー型として、コードリーダーによる詳細な分析を必要とせずにその意図された使用法を示すという利点があります。

35
Barry

共有アクセスが必要であるが共有所有権が必要でない場合

問題は、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の有理数。

30
Galik

ソースの自己文書化のためだけですか?

はい。

20
user541686

提案 から、std::observer_ptrは、ポインタがではなく(= /// =)非所有参照であることを文書化するためのものであるようです所有リファレンスarraystringまたはiterator

ただし、observer_ptr<T>ではなくT*を使用することには、他にもいくつかの利点があります。

  1. デフォルトで作成されたobserver_ptrは常にnullptrに初期化されます。通常のポインタは、コンテキストに応じて初期化される場合とされない場合があります。
  2. observer_ptrは、参照に対して意味のある演算のみをサポートします;これにより、正しい使用法が適用されます。
    • operator[]array操作であるため、observer_ptrには実装されていません。
    • これらはイテレータ操作であるため、observer_ptrではポインタ演算はできません。
  3. 2つのobserver_ptrsには、すべての実装で 厳密な弱い順序付け がありますが、これは2つの任意のポインターに対して保証されていません。これは、operator<std::lessobserver_ptr として実装されているためです( std::unique_ptr および std::shared_ptrと同様に / )。
  4. observer_ptr<void>はサポートされていないようです。そのため、より安全なソリューションの使用が促進される可能性があります(例: std::any および std::variant
16
Joseph Thomson

生のポインタに対してstd::observer_ptrを使用することの1つの素晴らしい結果は、Cから継承された、混乱しやすくエラーが発生しやすい複数のポインタのインスタンス化構文のより良い代替手段を提供することです。

std::observer_ptr<int> a, b, c;

の改善です

int *a, *b, *c;

これはC++の観点からは少し奇妙であり、次のように簡単にタイプミスすることができます

int* a, b, c;
5
Richard Forrest

はい、_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に基づくものもあるはずです。

2
Noah

ドキュメントの使用例とは別に、オブザーバーの装飾なしで生のポインターを渡すときに発生する可能性のある現実の問題があります。他のコードは、生のポインタに対して生涯の責任を誤って引き受け、std::unique_ptrstd::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演算子はオブザーバーに適用できません。

0
Robin R