Это хорошая практика, чтобы всегда использовать умные указатели? - PullRequest
72 голосов
/ 16 марта 2010

Я считаю, что умные указатели намного удобнее, чем необработанные указатели. Так что это хорошая идея, чтобы всегда использовать умные указатели? (Обратите внимание, что я из Java-фона и, следовательно, мне не очень нравится идея явного управления памятью. Поэтому, если у умных указателей нет серьезных проблем с производительностью, я бы хотел их придерживаться.)

Примечание. Несмотря на то, что я пришел из Java, я достаточно хорошо понимаю реализацию интеллектуальных указателей и концепций RAII. Таким образом, вы можете принять это знание как должное с моей стороны при публикации ответа. Я использую статическое распределение почти везде и использую указатели только при необходимости. Мой вопрос просто: Могу ли я всегда использовать умные указатели вместо необработанных указателей ???

Ответы [ 10 ]

69 голосов
/ 16 марта 2010

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

1. Когда не до

В двух ситуациях вам не следует использовать умные указатели.

Первая - это та же самая ситуация, в которой вы не должны использовать класс C++ на самом деле. IE: DLL граница, если вы не предлагаете исходный код клиенту. Скажем анекдотично.

Второе случается гораздо чаще: умный менеджер означает владение . Вы можете использовать указатели для указания на существующие ресурсы, не управляя их временем жизни, например:

void notowner(const std::string& name)
{
  Class* pointer(0);
  if (name == "cat")
    pointer = getCat();
  else if (name == "dog")
    pointer = getDog();

  if (pointer) doSomething(*pointer);
}

Этот пример ограничен. Но указатель семантически отличается от ссылки тем, что может указывать на недопустимое местоположение (нулевой указатель). В этом случае совершенно нормально не использовать смарт-указатель вместо него, потому что вы не хотите управлять временем жизни объекта.

2. Умные менеджеры

Если вы не пишете класс Smart Manager, если вы используете ключевое слово delete , вы делаете что-то неправильно.

Это спорная точка зрения, но после того, как я рассмотрел множество примеров некорректного кода, я больше не рискую. Итак, если вы пишете new, вам нужен умный менеджер для вновь выделенной памяти. И тебе это нужно прямо сейчас.

Это не значит, что вы меньше программист! Напротив, повторное использование кода, который, как было доказано, работает, вместо изобретения колеса снова и снова, является ключевым навыком.

Теперь начинается настоящая трудность: какой умный менеджер?

3. Умные указатели

Существуют различные умные указатели с различными характеристиками.

Пропуск std::auto_ptr, который вы должны избегать (его семантическая копия прикручена).

  • scoped_ptr: нет накладных расходов, не может быть скопировано или перемещено.
  • unique_ptr: нет накладных расходов, не может быть скопировано, может быть перемещено.
  • shared_ptr / weak_ptr: некоторые издержки (подсчет ссылок), могут быть скопированы.

Обычно попробуйте использовать либо scoped_ptr, либо unique_ptr. Если вам нужно несколько владельцев, попробуйте изменить дизайн. Если вы не можете изменить дизайн и действительно нуждаетесь в нескольких владельцах, используйте shared_ptr, но остерегайтесь циклов ссылок, которые должны быть прерваны с помощью weak_ptr где-то в середине.

4. Умные контейнеры

Многие интеллектуальные указатели не предназначены для копирования, поэтому их использование с контейнерами STL несколько скомпрометировано.

Вместо того, чтобы прибегать к shared_ptr и его издержкам, используйте умные контейнеры из Boost Pointer Container . Они эмулируют интерфейс классических контейнеров STL, но хранят собственные указатели.

5. Прокатись самостоятельно

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

Написание умного менеджера при наличии исключений довольно сложно. Обычно вы не можете предположить, что память доступна (new может дать сбой) или что Copy Constructor s имеет гарантию no throw.

Может быть, в некоторой степени приемлемо игнорировать исключение std::bad_alloc и навязывать, что Copy Constructor из ряда помощников не перестают работать ... в конце концов, именно это boost::shared_ptr делает для своего удалителя D параметр шаблона.

Но я бы не советовал, особенно для новичка. Это сложная проблема, и вы вряд ли заметите ошибки прямо сейчас.

6. Примеры

// For the sake of short code, avoid in real code ;)
using namespace boost;

// Example classes
//   Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
//   It is to obey the `Cloneable` concept as described in 
// the Boost Pointer Container library linked above
struct Cloneable
{
  virtual ~Cloneable() {}
  virtual Cloneable* clone() const = 0;
};

struct Derived: Cloneable
{
  virtual Derived* clone() const { new Derived(*this); }
};

void scoped()
{
  scoped_ptr<Cloneable> c(new Derived);
} // memory freed here

// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
  return unique_ptr<Cloneable>(new Derived);
}

void shared()
{
  shared_ptr<Cloneable> n1(new Derived);
  weak_ptr<Cloneable> w = n1;

  {
    shared_ptr<Cloneable> n2 = n1;          // copy

    n1.reset();

    assert(n1.get() == 0);
    assert(n2.get() != 0);
    assert(!w.expired() && w.get() != 0);
  } // n2 goes out of scope, the memory is released

  assert(w.expired()); // no object any longer
}

void container()
{
  ptr_vector<Cloneable> vec;
  vec.push_back(new Derived);
  vec.push_back(new Derived);

  vec.push_back(
    vec.front().clone()         // Interesting semantic, it is dereferenced!
  );
} // when vec goes out of scope, it clears up everything ;)
18 голосов
/ 16 марта 2010

Интеллектуальные указатели делают осуществляют явное управление памятью, и если вы не понимаете, как они это делают, у вас возникают проблемы при программировании на C ++. И помните, что память - не единственный ресурс, которым они управляют.

Но чтобы ответить на ваш вопрос, вы должны предпочесть смарт-указатели в качестве первого приближения к решению, но, возможно, будьте готовы отбросить их при необходимости. Вы никогда не должны использовать указатели (или любые другие) или динамическое распределение, когда этого можно избежать. Например:

string * s1 = new string( "foo" );      // bad
string s2( "bar" );    // good

Редактировать: Чтобы ответить на ваш дополнительный вопрос "Могу ли я всегда использовать умные указатели вместо необработанных указателей ??? Тогда нет, вы не можете. Если (например) вам нужно реализовать свой собственный версия оператора new, вам придется заставить его возвращать указатель, а не умный указатель.

13 голосов
/ 16 марта 2010

Обычно вы не должны использовать указатели (умные или иные), если они вам не нужны. Лучше сделать локальные переменные, члены класса, векторные элементы и подобные элементы обычными объектами вместо указателей на объекты. (Поскольку вы пришли из Java, вы, вероятно, соблазнитесь распределить все с помощью new, что не рекомендуется.)

Этот подход (" RAII ") избавляет вас от беспокойства о указателях большую часть времени.

Когда вам нужно использовать указатели, это зависит от ситуации и того, почему именно вам нужны указатели, но обычно можно использовать умные указатели. Это может быть не всегда (жирным шрифтом) будет лучшим вариантом, но это зависит от конкретной ситуации.

9 голосов
/ 16 марта 2010

Хорошее время , а не для использования интеллектуальных указателей, находится на границе интерфейса DLL.Вы не знаете, будут ли другие исполняемые файлы создаваться с помощью того же компилятора / библиотек.В соглашении о вызовах DLL вашей системы не будет указано, как выглядят стандартные классы или классы TR1, включая интеллектуальные указатели.

В исполняемом файле или библиотеке, если вы хотите представить владение указателем, интеллектуальные указатели в среднем являютсялучший способ сделать это.Поэтому хорошо всегда хотеть использовать их в предпочтении сырым.То, действительно ли вы всегда можете их использовать, - это другой вопрос.

Для конкретного примера, когда нет - предположим, что вы пишете представление общего графа с вершинами, представленными объектами, и ребрами, представленными указателями между объектами.Обычные интеллектуальные указатели вам не помогут: графики могут быть циклическими, и ни один конкретный узел не может нести ответственность за управление памятью других узлов, поэтому общих и слабых указателей недостаточно.Например, вы можете поместить все в вектор и использовать индексы вместо указателей, или поместить все в деке и использовать необработанные указатели.Вы можете использовать shared_ptr, если хотите, но это не добавит ничего, кроме накладных расходов.Или вы можете поискать GC.

Более маргинальный случай: я предпочитаю, чтобы функции принимали параметр по указателю или ссылке, и обещали не сохранять указатель или ссылку на него вместо того, чтобы взять shared_ptr, и вы задаетесь вопросом, могут ли они сохранить ссылку после того, как они вернутся, может быть, если вы когда-нибудь снова измените референд, вы что-то сломаете и т. д. Не удержание ссылок - это то, что часто не документируется.явно, это само собой разумеется.Может быть, не должно, но это так.Умные указатели подразумевают что-то о владении, и ложно подразумевают, что это может сбить с толку.Поэтому, если ваша функция принимает shared_ptr, не забудьте документировать, может ли она сохранить ссылку или нет.

6 голосов
/ 16 марта 2010

Во многих ситуациях я считаю, что это определенно верный путь (менее грязный код очистки, уменьшенный риск утечек и т. Д.). Однако есть некоторые очень незначительные дополнительные расходы. Если бы я писал какой-то код, который должен был быть максимально быстрым (скажем, плотным циклом, который должен был делать какое-то выделение и освобождение), я бы, вероятно, не использовал бы умный указатель в надежде набрать немного больше скорости. Но я сомневаюсь, что это будет иметь какое-то измеримое значение в большинстве ситуаций.

4 голосов
/ 16 марта 2010

В общем, нет, вы не можете использовать умные указатели всегда. Например, когда вы используете другие фреймворки, которые не используют умный указатель (например, Qt), вы также должны использовать необработанные указатели.

2 голосов
/ 16 марта 2010

Мой взгляд на умные указатели: ОТЛИЧНО, когда трудно понять, когда может произойти освобождение (скажем, внутри блока try / catch или внутри функции, которая вызывает функцию (или даже конструктор!), Которая может выбить вас из текущая функция), или добавление лучшего управления памятью к функции, которая возвращается в любом месте кода. Или положить указатели в контейнерах.

Умные указатели, однако, имеют стоимость, которую вы, возможно, не захотите платить по всей вашей программе. Если управление памятью легко выполнить вручную («Хм, я знаю, что когда эта функция заканчивается, мне нужно удалить эти три указателя, и я знаю, что эта функция будет работать до конца»), то зачем тратить время на то, чтобы компьютер делал это?

2 голосов
/ 16 марта 2010

Если вы обрабатываете ресурс, вы всегда должны использовать методы RAII, в случае с памятью - использовать ту или иную форму умного указателя (примечание: умный, а не shared_ptr, выберите умный указатель, наиболее подходит для вашего конкретного случая использования). Это единственный способ избежать утечек при наличии исключений.

Есть еще случаи, когда необработанные указатели необходимы, когда управление ресурсами не осуществляется через указатель. В частности, они являются единственным способом получения сбрасываемой ссылки. Подумайте о сохранении ссылки на объект, время жизни которого не может быть явно обработано (атрибут member, объект в стеке). Но это очень специфический случай, который я видел только один раз в реальном коде. В большинстве случаев использование shared_ptr является лучшим подходом для совместного использования объекта.

1 голос
/ 16 марта 2010

Да, НО я прошел несколько проектов без использования умного указателя или каких-либо указателей. Рекомендуется использовать такие контейнеры, как deque, list, map и т. Д. В качестве альтернативы я использую ссылки, когда это возможно. Вместо передачи указателя я передаю ссылку или константную ссылку, и почти всегда нелогично удалять / освобождать ссылку, поэтому у меня никогда не возникает проблем (обычно я создаю их в стеке, записывая { Class class; func(class, ref2, ref3); }

0 голосов
/ 13 июня 2017

Это так. Умный указатель является одним из краеугольных камней старой экосистемы Какао (Touch). Я считаю, что это продолжает влиять на новое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...