Смарт-указатель C ++ - PullRequest
       41

Смарт-указатель C ++

41 голосов
/ 17 января 2010

У меня есть несколько контейнеров в классе, например, вектор или карта, которые содержат shared_ptr для объектов, живущих в куче.

Например

template <typename T>
class MyExample
{
public:

private:
 vector<tr1::shared_ptr<T> > vec;
 map<tr1::shared_ptr<T> , int> h;
};

Я хочу иметь открытый интерфейс этого класса, который иногда возвращает shared_ptrs для константных объектов (через shared_ptr<const T>) и иногда shared_ptr<T> где Я позволяю звонящему мутировать объекты. Я хочу логической константности, поэтому, если я отмечу метод как const, он не может изменять объекты в куче.

Вопросы:

1) Меня смущает взаимозаменяемость tr1::shared_ptr<const T> и tr1::shared_ptr<T>. Когда кто-то передает shared_ptr<const T> shared_ptr в класс, я сохраняю его как shared_ptr<T> или shared_ptr<const T> внутри вектора и карты или меняю карту, векторные типы (например, insert_elemeent (shared_ptr<const T> obj)?

2) Лучше ли создавать экземпляры классов следующим образом: MyExample<const int>? Кажется чрезмерно ограничительный, потому что я никогда не смогу вернуть shared_ptr<int>?

Ответы [ 4 ]

36 голосов
/ 17 января 2010

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>.

11 голосов
/ 17 января 2010

Я бы предложил следующую методологию:

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);
    }
};

Это обеспечивает постоянную корректность. Как вы видите, метод add () использует не , а , потому что вы намерены хранить класс Ts, а не const Ts. Но при обращении к нему const вы возвращаете , что не составляет проблем, поскольку shared_ptr может быть легко преобразовано в shared_ptr . И так как оба метода 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);
    }
};
5 голосов
/ 17 января 2010

Если кто-то передает вам shared_ptr<const T>, вы никогда не сможете изменить T. Конечно, технически возможно привести const T к T, но это нарушает намерение сделать T const. Поэтому, если вы хотите, чтобы люди могли добавлять объекты в ваш класс, они должны давать вам shared_ptr<T>, а не 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.

3 голосов
/ 17 января 2010

одна вещь, которую нужно понять, это:

tr1::shared_ptr<const T> имитирует функциональность T const *, а именно то, на что он указывает - const, но сам указатель - нет.

Таким образом, вы можете назначить новое значение для вашего общего указателя, но я ожидаю, что вы не сможете использовать разыменованный shared_ptr в качестве l-значения.

...