web-dev-qa-db-ja.com

C ++スマートポインターconstの正確さ

私はクラスにいくつかのコンテナを持っています。たとえば、ヒープに存在するオブジェクトへのshared_ptrを含むベクターまたはマップです。

例えば

template <typename T>
class MyExample
{
public:

private:
    vector<shared_ptr<T> > vec_;
    map<shared_ptr<T>, int> map_;
};

shared_ptr<const T>を介して)shared_ptrsをconstオブジェクトに返し、場合によってはshared_ptr<T>で呼び出し元がオブジェクトを変更できるようにする、このクラスのパブリックインターフェイスが必要です。

論理的なconstの正確さを求めているため、メソッドをconstとしてマークしても、ヒープ上のオブジェクトを変更できません。

質問:

1)shared_ptr<const T>shared_ptr<T>の互換性に混乱しています。誰かがshared_ptr<const T>をクラスに渡すとき、私は次のことを行います:

  • コンテナ内にshared_ptr<T>またはshared_ptr<const T>として保存しますか?
  • OR
  • マップ、ベクタータイプを変更しますか(例:insert_element(shared_ptr<const T> obj)?

2)次のようにクラスをインスタンス化する方が良いですか:MyExample<const int>?私はshared_ptr<int>?を返すことができないので、それは過度に制限的なようです。

42
user231536

shared_ptr<T>shared_ptr<const T>not交換可能です。それは一方通行です-shared_ptr<T>shared_ptr<const T>に変換できますが、その逆はできません。

観察する:

// f.cpp

#include <memory>

int main()
{
    using namespace std;

    shared_ptr<int> pint(new int(4)); // normal shared_ptr
    shared_ptr<const int> pcint = pint; // shared_ptr<const T> from shared_ptr<T>
    shared_ptr<int> pint2 = pcint; // error! comment out to compile
}

経由でコンパイル

cl/EHsc f.cpp

定数に基づいて関数をオーバーロードすることもできます。これらの2つの事実を組み合わせて、やりたいことを行うことができます。

2番目の質問については、MyExample<int>MyExample<const int>よりもおそらく意味があります。

37
Terry Mahaffey

以下の方法論を提案します。

template <typename T>
class MyExample
{
  private:
    vector<shared_ptr<T> > data;

  public:
    shared_ptr<const T> get(int idx) const
    {
      return data[idx];
    }
    shared_ptr<T> get(int idx)
    {
      return data[idx];
    }
    void add(shared_ptr<T> value)
    {
      data.Push_back(value);
    }
};

これにより、const-正確さが保証されます。ご覧のように、add()メソッドは<const T>ではなく<T>を使用します。これは、クラスにTを格納するためであり、const Tsではないためです。しかし、constにアクセスすると、<const T>が返されます。shared_ptr<T>は簡単にshared_ptr <const T>に変換できるため、問題ありません。そして、どちらのget()メソッドも、内部ストレージ内のshared_ptrのコピーを返すので、呼び出し元は、内部ポインタが指すオブジェクトを誤って変更できません。これはすべて、非スマートポインターバリアントに匹敵します。

template <typename T>
class MyExamplePtr
{
  private:
    vector<T *> data;

  public:
    const T *get(int idx) const
    {
      return data[idx];
    }
    T *get(int idx)
    {
      return data[idx];
    }
    void add(T *value)
    {
      data.Push_back(value);
    }
};
12
mmmmmmmm

理解しておくべきことの1つは、

tr1::shared_ptr<const T>T const *の機能を模倣しています。つまり、それが指すのはconstですが、ポインタ自体はそうではありません。

したがって、共有ポインタに新しい値を割り当てることができますが、逆参照されたshared_ptrをl値として使用することはできないと思います。

3
Evan Teran

プロローグ

const修飾子は、レガシーCポインターに影響を与えるのと同じように、std::shared_ptrの動作を変更します。

スマートポインターは、常に適切な修飾子を使用して管理および保存し、プログラマーがそれらを正しく処理することを防止、強制、および支援する必要があります。

答え

  1. 誰かがshared_ptr<const T>をクラスに渡すと、それをshared_ptr<T>またはshared_ptr<const T>としてベクター内に格納してマップしますか、それともマップ、ベクタータイプを変更しますか?

APIがshared_ptr<const T>を受け入れる場合、呼び出し元と自分の間の暗黙の契約では、ポインターが指すTオブジェクトを変更することは許可されないため、そのように保持する必要があります。内部コンテナ、例えばstd::vector<std::shared_ptr<const T>>

さらに、プログラムでこれを実現することはできますが、モジュールがstd::shared_ptr<T>を返すことは決して許可/許可されるべきではありません(方法については、2番目の質問に対する私の回答を参照してください)。

  1. 次のようにクラスをインスタンス化する方が良いですか:MyExample<const int>?私はshared_ptr<int>?を返すことができないので、それは過度に制限的なようです。

場合によります:

  • 渡されたオブジェクトが将来再び変更されないようにモジュールを設計した場合は、const Tを基本型として使用してください。

  • モジュールが非const Tポインターを返すことができる必要がある場合は、Tを基本型として使用し、おそらく2つの異なるゲッター、1つは可変オブジェクトを返す(std::shared_ptr<T>)と、変更できないオブジェクトを返す別のオブジェクト(std::shared_ptr<const T>)。

そして、私があなたに同意したことを願っていますがすべきではありませんstd::shared_ptr<T>を返しますconst Tまたはstd::shared_ptr<const T>がある場合、あなたはcan

const T a = 10;
auto a_ptr = std::make_shared<T>(const_cast<T>(a));

auto b_const_ptr = std::make_shared<const T>();
auto b_ptr = std::const_pointer_cast<T>(b_const_ptr);

本格的な例

std::shared_ptrでのconstの考えられるすべての順列をカバーする次の例を検討してください。

struct Obj
{
  int val = 0;
};

int main()
{
    // Type #1:
    // ------------
    // Create non-const pointer to non-const object
    std::shared_ptr<Obj> ptr1 = std::make_shared<Obj>();
    // We can change the underlying object inside the pointer
    ptr1->val = 1;
    // We can change the pointer object
    ptr1 = nullptr;

    // Type #2:
    // ------------
    // Create non-const pointer to const object
    std::shared_ptr<const Obj> ptr2 = std::make_shared<const Obj>();
    // We cannot change the underlying object inside the pointer
    ptr2->val = 3; // <-- ERROR
    // We can change the pointer object
    ptr2 = nullptr;

    // Type #3:
    // ------------
    // Create const pointer to non-const object
    const std::shared_ptr<Obj> ptr3 = std::make_shared<Obj>();
    // We can change the underlying object inside the pointer
    ptr3->val = 3;
    // We can change the pointer object
    ptr3 = nullptr; // <-- ERROR

    // Type #4:
    // ------------
    // Create const pointer to non-const object
    const std::shared_ptr<const Obj> ptr4 = std::make_shared<const Obj>();
    // We can change the underlying object inside the pointer
    ptr4->val = 4; // <-- ERROR
    // We can change the pointer object
    ptr4 = nullptr; // <-- ERROR

    // Assignments:
    // ------------
    // Conversions between objects
    // We cannot assign to ptr3 and ptr4, because they are const
    ptr1 = ptr4 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
    ptr1 = ptr3;
    ptr1 = ptr2 // <-- ERROR, cannot convert 'const Obj' to 'Obj'

    ptr2 = ptr4;
    ptr2 = ptr3;
    ptr2 = ptr1;
}

:スマートポインターのすべてのタイプを管理する場合、次のことが当てはまります。ポインターの割り当ては異なる場合があります(たとえば、unique_ptrを処理する場合)。ただし、概念は同じです。

0
Daniel Trugman