Правильно ли использовать интеллектуальные указатели?

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

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

Ответ 1

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

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. Умные менеджеры

Если вы не пишете класс интеллектуального менеджера, если вы используете ключевое слово 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 имеет гарантию no throw.

Может быть приемлемым несколько игнорировать исключение std::bad_alloc и навязывать, что Copy Constructor ряда помощников не терпят неудачу... в конце концов, что boost::shared_ptr делает для своего шаблона deteter 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 ;)

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

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