Корректность курсора
У меня есть несколько контейнеров в классе, например, вектор или карта, которые содержат
shared_ptr для объектов, живущих в куче.
Например,
template <typename T>
class MyExample
{
public:
private:
vector<shared_ptr<T> > vec_;
map<shared_ptr<T>, int> map_;
};
Я хочу иметь открытый интерфейс этого класса, который иногда возвращает shared_ptrs для константных объектов (через shared_ptr<const T>
) и иногда shared_ptr<T>
, где я позволяю вызывающей стороне изменять объекты.
Мне нужна логическая корректность const, поэтому, если я помечаю метод как const, он не может изменить объекты в куче.
Вопросы:
1) Меня смущает взаимозаменяемость shared_ptr<const T>
и shared_ptr<T>
. Когда кто-то передает shared_ptr<const T>
в класс, я должен:
- Хранить его как
shared_ptr<T>
или shared_ptr<const T>
внутри контейнера?
- ИЛИ
- Могу ли я изменить карту, векторные типы (например, insert_element (
shared_ptr<const T>
obj)?
2) Лучше ли создавать экземпляры классов следующим образом: MyExample<const int>
? Это кажется чрезмерно ограничительным, потому что я никогда не смогу вернуть shared_ptr<int>
?
Ответы
Ответ 1
shared_ptr<T>
и shared_ptr<const T>
являются не взаимозаменяемыми. Он идет в одну сторону - 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
Вы также можете перегрузить функцию, основанную на константе. Вы можете комбинировать эти два факта, чтобы делать то, что хотите.
Что касается вашего второго вопроса, MyExample<int>
, вероятно, имеет больше смысла, чем MyExample<const int>
.
Ответ 2
Я бы предложил следующую методологию:
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-correctness. Как вы видите, метод add() не использует < const T > but <T> , потому что вы намерены хранить класс Ts not const Ts. Но при обращении к нему const вы возвращаете < const T > , что не представляет проблемы, поскольку shared_ptr <T> может быть легко преобразован в shared_ptr < const T > . И sice методы 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);
}
};
Ответ 3
Если кто-то передает вам shared_ptr<const T>
, вы никогда не сможете изменять T
. Разумеется, технически возможно использовать const T
только a T
, но это нарушает намерение сделать T
const
. Поэтому, если вы хотите, чтобы люди могли добавлять объекты в ваш класс, они должны давать вам shared_ptr<T>
и no shared_ptr<const T>
. Когда вы возвращаете вещи из своего класса, вы не хотите изменять их, то есть когда вы используете shared_ptr<const T>
.
shared_ptr<T>
можно автоматически преобразовать (без явного приведения) в shared_ptr<const T>
, но не наоборот. Это может помочь вам (и вы должны сделать это в любом случае), чтобы либерально использовать методы const
. Когда вы определяете метод класса const
, компилятор не позволит вам изменять какой-либо из ваших членов данных или возвращать что-либо, кроме const T
. Поэтому использование этих методов поможет вам убедиться, что вы ничего не забыли, и поможет пользователям вашего класса понять, что представляет собой цель метода. (Пример: virtual shared_ptr<const T> myGetSharedPtr(int index) const;
)
Вы верны в своем втором заявлении, вы, вероятно, не хотите создавать экземпляр своего класса как <const T>
, так как вы никогда не сможете изменить какой-либо из ваших T
s.
Ответ 4
можно понять одно:
tr1::shared_ptr<const T>
имитирует функциональность T const *
, а именно то, на что он указывает, является константой, но сам указатель не является.
Итак, вы можете назначить новое значение для вашего общего указателя, но я бы ожидал, что вы не сможете использовать разыменованный shared_ptr
как l-значение.
Ответ 5
Пролог
Спецификатор const
изменяет поведение 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>
, даже если это можно достичь программным путем (см. мой ответ на второй вопрос, чтобы узнать как).
-
Лучше ли создавать экземпляры классов следующим образом:
MyExample<const int>
? Это кажется чрезмерно ограничительным, потому что я никогда не смогу вернуть shared_ptr<int>
?
Это зависит от:
Если вы спроектировали свой модуль таким образом, чтобы передаваемые ему объекты не изменялись в будущем, используйте const T
в качестве базового типа.
Если ваш модуль должен иметь возможность возвращать неконстантные указатели T
, вы должны использовать T
в качестве базового типа и, вероятно, иметь два разных метода получения, один, который возвращает изменяемые объекты (std::shared_ptr<T>
), а другой, который возвращает не- изменяемые объекты (std::shared_ptr<const T>
).
И хотя я надеюсь, что мы только что согласились, вы не должны возвращать std::shared_ptr<T>
, если у вас есть const T
или std::shared_ptr<const T>
, вы можете:
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);
Полноценный пример
Рассмотрим следующий пример, который охватывает все возможные перестановки const
с std::shared_ptr
:
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
), но концепция его та же.