17

std::vectorat()は、の安全な代替手段としてメンバー関数を持っているoperator[]ため、境界チェックが適用され、ダングリング参照が作成されません。

void foo(std::vector<int> const&x)
{
  const auto&a=x[0];     // What if x.empty()? Undefined behavior!
  const auto&a=x.at(0);  // Throws exception if x.empty().
}

ただし、std::unique_ptr対応する機能がありません。

void foo(std::unique_ptr<int> const&x)
{
  const auto&a=*x;       // What if bool(x)==false? Undefined behavior!
}

このような安全な代替手段があればstd::unique_ptr、たとえば member ref()(and cref()) のように、ぶら下がっている参照を返すことはなく、例外をスローすることができれば、それは素晴らしいことです。可能な実装:

template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
  if(bool(*this)==false)
    throw run_time_error("trying to de-refrence null unique_ptr");
  return this->operator*();
}

標準がこの種のものを提供しない正当な理由はありますか?

4

6 に答える 6

17

unique_ptrnull 状態検出を備えた軽量のポインター クラスとして特別に設計されました (たとえば、オプション オブジェクトを表すユーティリティ クラスを追加する提案 (改訂 3) のオプションに記載されています) 。

そうは言っても、 operator*のドキュメントには次のように記載されているため、あなたが求めている機能はすでに整っています。

// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;

pointerタイプは次のように定義されます。

std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*

したがって、カスタム デリーターを使用すると、null ポインターのチェックや例外のスローを含むオンザフライ操作を実行できます。

#include <iostream>
#include <memory>

struct Foo { // object to manage
    Foo() { std::cout << "Foo ctor\n"; }
    Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
    Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
    ~Foo() { std::cout << "~Foo dtor\n"; }
};

struct Exception {};

struct InternalPtr {
    Foo *ptr = nullptr;
    InternalPtr(Foo *p) : ptr(p) {}
    InternalPtr() = default;

    Foo& operator*() const {
        std::cout << "Checking for a null pointer.." << std::endl;
        if(ptr == nullptr)
            throw Exception();
        return *ptr;
    }

    bool operator != (Foo *p) {
        if(p != ptr)
            return false;
        else
            return true;
    }
    void cleanup() {
      if(ptr != nullptr)
        delete ptr;
    }
};

struct D { // deleter
    using pointer = InternalPtr;
    D() {};
    D(const D&) { std::cout << "D copy ctor\n"; }
    D(D&) { std::cout << "D non-const copy ctor\n";}
    D(D&&) { std::cout << "D move ctor \n"; }
    void operator()(InternalPtr& p) const {
        std::cout << "D is deleting a Foo\n";
        p.cleanup();
    };
};

int main()
{
    std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved

    try {
      auto& e = *up;      
    } catch(Exception&) {
        std::cout << "null pointer exception detected" << std::endl;
    }

}

Live Example

完全を期すために、2 つの追加の代替手段/回避策を投稿します。

  1. unique_ptrビアのポインタチェックoperator bool

    #include <iostream>
    #include <memory>
    
    int main()
    {
        std::unique_ptr<int> ptr(new int(42));
    
        if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';
        ptr.reset();
        if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n';
    }
    

    (これはおそらく、この問題に対処するための最も巧妙な方法です)

  2. 別の解決策は、面倒ですが、例外処理を処理するラッパータイプを使用することです

于 2015-08-18T08:40:08.490 に答える
13

本当の答えは単純で、多くの「なぜ C++ はこのようにならないのか?」と同じ答えだと思います。質問:

誰もそれを提案しませんでした。

std::vectorstd::unique_ptr同時に同じ人によって設計されているわけではなく、同じ方法で使用されているわけでもないため、必ずしも同じ設計原則に従っているとは限りません。

于 2015-08-18T09:03:11.627 に答える
4

なぜ委員会が安全な参照解除メソッドを追加しないことにしたのか、私にはわかりません。答えはおそらく「提案されていないため」または「生のポインタにもないため」です。しかし、引数として任意のポインターを取り、それを nullptr と比較してから、例外をスローするか、ポイントされたオブジェクトへの参照を返す無料の関数テンプレートを独自に作成するのは簡単です。

基本クラスへのポインターを介して削除しない場合は、からパブリックに派生させて、unique_ptrそのようなメンバー関数を追加することさえ可能です。

ただし、そのようなチェックされたメソッドをどこでも使用すると、パフォーマンスが大幅に低下する可能性があることに注意してください (at と同じ)。通常、パラメーターを検証するのは多くても 1 回です。これには、最初に単一の if ステートメントを使用する方が適しています。

また、プログラミング エラーに対応して例外をスローしてはならないという学校もあります。おそらく、デザインを担当unique_ptrする人々はこの学校に属していましたが、ベクターをデザインしていた人々 (はるかに古い) はそうではありませんでした。

于 2015-08-18T08:54:05.767 に答える
3

スマート ポインター API 設計の主な目標の 1 つは、付加価値のある簡単な置き換えであり、落とし穴や副作用がなく、オーバーヘッドがほぼゼロであることです。if (ptr) ptr->...ベア ポインターへの安全なアクセスが通常行われる方法は次のとおりです。同じ構文がスマート ポインターで適切に機能するため、一方が他方に置き換えられたときにコードを変更する必要はありません。

ポインター内に追加された妥当性チェック (例外をスローするなど) は、分岐予測子に干渉し、パフォーマンスに影響を与える可能性があります。

于 2015-08-18T08:45:12.130 に答える
2

あなたは持っています

operator bool()

例: cplusplusreference

// example of unique_ptr::operator bool
#include <iostream>
#include <memory>


int main () {
  std::unique_ptr<int> foo;
  std::unique_ptr<int> bar (new int(12));

  if (foo) std::cout << "foo points to " << *foo << '\n';
  else std::cout << "foo is empty\n";

  if (bar) std::cout << "bar points to " << *bar << '\n';
  else std::cout << "bar is empty\n";

  return 0;
}

unique_ptr は生のポインターへの単純なラッパーです。ブール条件を簡単にチェックできる場合は、例外をスローする必要はありません。

編集: どうやらoperator* がスローできるようです。

例外 1) スローされる可能性があります。たとえば、ポインターがスローする演算子を定義している場合*

たぶん、誰かが投げるオペレーターを定義するためにホットにいくつかの光を当てることができます*

于 2015-08-18T08:28:46.623 に答える