web-dev-qa-db-ja.com

スマートポインターよりも生のポインターを使用する場合

この回答 を読んだ後、 スマートポインター を可能な限り使用し、「通常」/ rawポインターの使用を最小限に抑えることがベストプラクティスのようです。

本当?

55
Alon Gubkin

いいえ、そうではありません。関数がポインターを必要とし、所有権とは何の関係もない場合、次の理由で通常のポインターを渡す必要があると強く信じています。

  • 所有権がないため、どのような種類のスマートポインターを渡すかわからない
  • shared_ptrなどの特定のポインターを渡すと、scoped_ptrなどを渡すことができなくなります

ルールはこれです-エンティティがオブジェクトの特定の種類の所有権を取得する必要があることがわかっている場合、always useスマートポインター-必要な所有権を提供します。所有権の概念がない場合、neverスマートポインターを使用します。

例1:

void PrintObject(shared_ptr<const Object> po) //bad
{
    if(po)
      po->Print();
    else
      log_error();
}

void PrintObject(const Object* po) //good
{
    if(po)
      po->Print();
    else
      log_error();
}

例2:

Object* createObject() //bad
{
    return new Object;
}

some_smart_ptr<Object> createObject() //good
{
   return some_smart_ptr<Object>(new Object);
}
80
Armen Tsirunyan

スマートポインターは所有権を明確に文書化するため、常に使用することをお勧めします。

ただし、実際に見逃しているのは、「空の」スマートポインターであり、所有権の概念を示唆するものではありません。

template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
  ptr(T* p): _p(p) {}
  template <typename U> ptr(U* p): _p(p) {}
  template <typename SP> ptr(SP const& sp): _p(sp.get()) {}

  T& operator*() const { assert(_p); return *_p; }
  T* operator->() const { assert(_p); return _p; }

private:
  T* _p;
}; // class ptr<T>

これは確かに、存在する可能性のあるスマートポインターの最も単純なバージョンです。つまり、それが指すリソースも所有していないことを文書化するタイプです。

6
Matthieu M.

参照カウント(特にshared_ptrで使用)が壊れる1つの例は、ポインターからサイクルを作成するときです(例:AポイントからB、BポイントからA、またはA-> B-> C-> A、または等)。その場合、すべてのオブジェクトが相互の参照カウントをゼロより大きく維持しているため、オブジェクトは自動的に解放されません。

そのため、親子関係を持つオブジェクト(オブジェクトのツリーなど)を作成するときは常に、親オブジェクトでshared_ptrsを使用して子オブジェクトを保持しますが、子オブジェクトに親へのポインタが必要な場合は、そのためにプレーンなC/C++ポインターを使用します。

4
Jeremy Friesner

私はここでもう少し徹底的な答えが与えられたと思います: どの種類のポインタを使用しますか?

そのリンクからの抜粋:「ダムポインター(生のポインター)またはリソースへの参照の参照を使用しますリソースへ、およびリソースは、参照元のオブジェクト/スコープよりも長持ちします。 (オリジナルから保存された太字)

問題は、一般的な使用のためのコードを書いている場合、オブジェクトが生のポインタよりも長生きすることを絶対に確実にすることは必ずしも容易ではないことです。この例を考えてみましょう:

_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_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
    employee_list.clear();
    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("John", "Smith"));
    current_employee_list.Push_back(employee_t("Julie", "Jones"));
    employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());

    replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}
_

驚いたことに、replace_current_employees_with()関数は、使用を終了する前にパラメータの1つが誤って割り当て解除される可能性があります。

したがって、最初はreplace_current_employees_with()関数はパラメータの所有権を必要としないように見えますが、パラメータの使用が完了する前に、パラメータが潜在的に割り当て解除される可能性に対する何らかの防御が必要です。最も単純な解決策は、おそらく_shared_ptr_を介して、実際にパラメーターの(一時的な)所有権を取得することです。

しかし、本当に所有権を取得したくない場合は、安全なオプションがあります-これは答えの恥知らずなプラグ部分です-" 登録済みポインタ "。 「登録済みポインター」は、生のポインターのように動作するスマートポインターです。ただし、ターゲットオブジェクトが破棄されると(自動的に)_null_ptr_に設定され、デフォルトでは、オブジェクトにアクセスしようとすると例外がスローされます。既に削除されています。

また、登録済みのポインターは、コンパイル時のディレクティブで「無効化」(対応する生のポインターに自動的に置き換える)できるため、デバッグ/テスト/ベータモードでのみ使用(およびオーバーヘッドが発生)できることに注意してください。したがって、実際の生のポインタを使用することはほとんどありません。

1
Noah

いくつかの場合、ポインターを使用する場合があります。

  • 関数ポインター(明らかにスマートポインターなし)
  • 独自のスマートポインターまたはコンテナーの定義
  • 生のポインターが重要である低レベルのプログラミングに対処する
  • 生配列からの減衰
1
iammilind