Подтвердить что ты не робот

Корректность курсора

У меня есть несколько контейнеров в классе, например, вектор или карта, которые содержат 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>?

4b9b3361

Ответ 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 так же, как оно влияет на устаревшие указатели языка Си.

Интеллектуальные указатели должны всегда управляться и храниться с использованием правильных классификаторов, чтобы программисты могли правильно их обрабатывать, применять и помогать программистам.

ответы

  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>, даже если это можно достичь программным путем (см. мой ответ на второй вопрос, чтобы узнать как).

  1. Лучше ли создавать экземпляры классов следующим образом: 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), но концепция его та же.