私はクラスにいくつかのコンテナを持っています。たとえば、ヒープに存在するオブジェクトへの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>
として保存しますか?shared_ptr<const T>
obj)?2)次のようにクラスをインスタンス化する方が良いですか:MyExample<const int>
?私はshared_ptr<int>
?を返すことができないので、それは過度に制限的なようです。
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>
よりもおそらく意味があります。
以下の方法論を提案します。
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);
}
};
理解しておくべきことの1つは、
tr1::shared_ptr<const T>
はT const *
の機能を模倣しています。つまり、それが指すのはconstですが、ポインタ自体はそうではありません。
したがって、共有ポインタに新しい値を割り当てることができますが、逆参照されたshared_ptr
をl値として使用することはできないと思います。
const
修飾子は、レガシーCポインターに影響を与えるのと同じように、std::shared_ptr
の動作を変更します。
スマートポインターは、常に適切な修飾子を使用して管理および保存し、プログラマーがそれらを正しく処理することを防止、強制、および支援する必要があります。
- 誰かが
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番目の質問に対する私の回答を参照してください)。
- 次のようにクラスをインスタンス化する方が良いですか:
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
を処理する場合)。ただし、概念は同じです。