Почему библиотеки и фреймворки C ++ никогда не используют умные указатели?

156

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

Однако я заметил, что фреймворки, такие как Qt, wxWidgets и библиотеки, такие как Boost, никогда не возвращают и не ожидают интеллектуальных указателей, как если бы они вообще не использовали их. Вместо этого они возвращаются или ожидают сырых указателей. Есть ли причина для этого? Должен ли я держаться подальше от умных указателей при написании общедоступного API и почему?

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

Laurent
источник
22
Все те библиотеки, которые вы только что назвали, были созданы много лет назад. Умные указатели стали по-настоящему стандартными в C ++ 11.
chrisaycock
22
Интеллектуальные указатели имеют накладные расходы (подсчет ссылок и т. д.), которые могут быть критическими, например, во встроенных системах / системах реального времени. ИМХО - умные указатели для ленивых программистов. Также много API-интерфейсов идут для наименьшего общего знаменателя. Я чувствую, как пламя лижет мои ноги, когда я печатаю!
Эд Хил
93
@EdHeal: причина, по которой вы чувствуете, как пламя лижет ваши ноги, заключается в том, что вы абсолютно неправы во всех отношениях. Например, какие накладные расходы там unique_ptr? Никак нет. Предназначены ли Qt / WxWidgets для встроенных систем или систем реального времени? Нет, они предназначены для Windows / Mac / Unix на рабочем столе - максимум. Умные указатели предназначены для программистов, которые хотят, чтобы это было правильно.
Щенок
24
Действительно, мобильные телефоны работают под управлением Java.
Р. Мартиньо Фернандес
12
Умные указатели только действительно стандартны в C ++ 11? Какой??? Эти вещи используются уже более 20 лет.
Каз

Ответы:

124

Помимо того, что многие библиотеки были написаны до появления стандартных интеллектуальных указателей, основной причиной, вероятно, является отсутствие стандартного двоичного интерфейса приложений C ++ (ABI).

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

Но из-за отсутствия стандартного ABI вы, как правило, не можете безопасно передавать эти объекты через границы модулей. GCC shared_ptr, вероятно, отличается от MSVC shared_ptr, который также может отличаться от Intel shared_ptr. Даже с одним и тем же компилятором эти классы не гарантированно двоично совместимы между версиями.

Суть в том, что если вы хотите распространять предварительно собранную версию вашей библиотеки, вам нужен стандартный ABI, на который можно положиться. У C его нет, но поставщики компиляторов очень хорошо разбираются во взаимодействии между библиотеками C для данной платформы - существуют стандарты де-факто.

Ситуация не так хороша для C ++. Отдельные компиляторы могут обрабатывать взаимодействие между своими собственными двоичными файлами, поэтому у вас есть возможность распространять версию для каждого поддерживаемого компилятора, часто GCC и MSVC. Но в свете этого, большинство библиотек просто экспортируют интерфейс C - и это означает, что указатели являются необработанными.

Небиблиотечный код должен, однако, обычно предпочитать умные указатели необработанным.

Джон Перди
источник
17
Я согласен с вами, даже передача std :: string может быть трудной задачей. Это многое говорит о C ++ как о «отличном языке для библиотек».
Ha11owed
8
Суть заключается в следующем: если вы хотите распространять предварительно собранную версию, вы должны делать это для каждого компилятора, который вы хотите поддерживать.
josefx
6
@josefx: Да, это печально, но это правда, единственная альтернатива - это COM или простой интерфейс C. Я хотел бы, чтобы C ++ начал беспокоиться о подобных проблемах. Я имею в виду, что C ++ не является новым языком 2 года назад.
Robot Mess
3
Я понизил, потому что это неправильно. Проблемы ABI более чем управляемы в большинстве случаев. Несмотря на то, что ABI едва ли удобна для пользователя, она также трудно преодолеть.
Щенок
4
@NathanAdams: Такое программное обеспечение, несомненно, впечатляет и полезно. Но это относится к симптому более глубоких проблем: семантика C ++ времени жизни и владения где-то между бедной и несуществующей. Эти кучи ошибок не возникли бы, если бы язык не позволял им. Разумеется, умные указатели - это не панацея, а попытка возместить некоторые потери, которые несет C ++.
Джон Перди
40

Там может быть много причин. Чтобы перечислить несколько из них:

  1. Умные указатели стали частью стандарта совсем недавно. До этого они были частью других библиотек.
  2. Их основное назначение - избежать утечек памяти; у многих библиотек нет собственного управления памятью; Как правило, они предоставляют утилиты и API
  3. Они реализованы как обертка, так как они на самом деле являются объектами, а не указателями. Который имеет дополнительные затраты времени / пространства по сравнению с необработанными указателями; Пользователи библиотек могут не захотеть иметь такие накладные расходы

Редактировать : Использование умных указателей - полностью выбор разработчика. Это зависит от различных факторов.

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

  2. В проекте, который нуждается в обратной совместимости, вы можете не захотеть использовать умные указатели, которые имеют специфические особенности C ++ 11

Edit2 В течение 24 часов есть ряд отрицательных голосов из-за нижнего прохода. Я не понимаю, почему за этот ответ проголосовали, хотя ниже приведено лишь дополнительное предложение, а не ответ.
Тем не менее, C ++ всегда облегчает вам открывать опции. :) например

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

И в вашем коде используйте это как:

Pointer<int>::type p;

Для тех, кто говорит, что умный указатель и необработанный указатель разные, я согласен с этим. Приведенный выше код был просто идеей, где можно написать код, который взаимозаменяем только с a #define, это не принуждение ;

Например, T*должен быть удален явно, а умный указатель - нет. Мы можем иметь шаблон, Destroy()чтобы справиться с этим.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

и использовать его как:

Destroy(p);

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

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Где Assign()это как:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}
iammilind
источник
14
3. Некоторые интеллектуальные указатели имеют дополнительные затраты времени / пространства, другие - нет, в том числе те, std::auto_ptrкоторые долгое время были частью стандарта (и обратите внимание, мне нравится std::auto_ptrв качестве типа возврата для функций, создающих объекты, даже если это почти бесполезен везде) В C ++ 11 std::unique_ptrнет дополнительных затрат по сравнению с простым указателем.
Дэвид Родригес - dribeas
4
Точно ... есть приятная симметрия в появлении unique_ptrи исчезновении auto_ptr, код, нацеленный на C ++ 03, должен использовать более поздний, в то время как код, нацеленный на C ++ 11, может использовать первый. Умных указателей нет shared_ptr , есть много стандартных и ни одного стандарта, включая предложения к стандарту, которые были отклонены какmanaged_ptr
Дэвид Родригес - dribeas
2
@iammilind, это интересные моменты, но забавно то, что если мы в конечном итоге будем использовать умные указатели, как, по-видимому, многие рекомендовали бы, мы в конечном итоге создадим код, несовместимый с основными библиотеками. Конечно, мы можем обернуть / развернуть умные указатели по мере необходимости, но это создает много хлопот и создает непоследовательный код (иногда мы имеем дело с умными указателями, иногда нет).
Лоран
7
Утверждение, что умные указатели имеют «дополнительные затраты времени / пространства», несколько вводит в заблуждение; Все умные указатели, за исключением unique_ptrзатрат времени выполнения, unique_ptrбезусловно, являются наиболее часто используемыми. Пример кода вы предоставляете также вводит в заблуждение, потому что unique_ptrи T*это совершенно разные понятия. Тот факт, что вы ссылаетесь на них обоих, typeсоздает впечатление, что они могут быть заменены друг на друга.
указатель на пустоту
12
Вы не можете печатать так, эти типы никоим образом не эквивалентны. Написание typedefs, как это, вызывает проблемы.
Алекс Б
35

С умными указателями есть две проблемы (до C ++ 11):

  • нестандартные, поэтому каждая библиотека имеет тенденцию заново изобретать свои собственные (проблемы с синдромом NIH и проблемы с зависимостями)
  • потенциальная стоимость

По умолчанию смарт - указатель, в том , что она является экономически свободным есть unique_ptr. К сожалению, это требует семантики перемещения C ++ 11, которая появилась только недавно. Все другие умные указатели имеют стоимость ( shared_ptr, intrusive_ptr) или имеют менее чем идеальную семантику ( auto_ptr).

С C ++ 11 за углом, в результате чего std::unique_ptr, можно было бы подумать, что он наконец закончился ... Я не такой оптимистичный.

Только несколько крупных компиляторов реализуют большую часть C ++ 11 и только в своих последних версиях. Мы можем ожидать, что основные библиотеки, такие как QT и Boost, будут некоторое время сохранять совместимость с C ++ 03, что несколько препятствует широкому внедрению новых и блестящих интеллектуальных указателей.

Матье М.
источник
12

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

Библиотеки обычно либо возвращают значение, либо заполняют объект. У них обычно нет объектов, которые нужно использовать во многих местах, поэтому им не нужно использовать умные указатели (по крайней мере, не в их интерфейсе, они могут использовать их внутри).

Я мог бы взять в качестве примера библиотеку, над которой мы работали, где после нескольких месяцев разработки я понял, что мы использовали указатели и умные указатели только в нескольких классах (3-5% всех классов).

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

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

Робот беспорядок
источник
Я не согласен с вами, но я укажу, что существует школа мысли, которая в большинстве случаев не одобряет передачу переменных ссылок. Признаюсь, я придерживаюсь этой школы. Я предпочитаю функции не изменять свои аргументы. Во всяком случае, насколько мне известно, ссылки на переменные в C ++ ничего не делают для предотвращения неправильного обращения с объектами, на которые они ссылаются, что и намерены делать умные указатели.
THB
2
у вас есть const для этого (кажется, я могу комментировать: D).
Робот беспорядок
9

Qt бессмысленно заново изобрел многие части стандартной библиотеки, пытаясь стать Java. Я считаю, что сейчас у него действительно есть свои умные указатели, но в целом это вряд ли вершина дизайна. Насколько я знаю, wxWidgets был разработан задолго до того, как были написаны полезные умные указатели.

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

Кроме того, не забывайте, что существуют умные указатели для обеспечения прав собственности. Если у API нет семантики владения, зачем использовать умный указатель?

щенок
источник
19
Qt был написан до того, как большая часть функциональности была достаточно широко распространена на платформах, которые он хотел использовать. У него были умные указатели в течение долгого времени, и он использует их для неявного совместного использования ресурсов почти во всех классах Q *.
rubenvb
6
Каждая библиотека GUI без необходимости заново изобретает колесо. Даже строки, у Qt QString, у wxWidgets wxString, у MFC ужасно названные CString. Разве UTF-8 не std::stringподходит для 99% задач с графическим интерфейсом?
обратный
10
@Inverse QString была создана, когда рядом не было std :: string.
MrFox
Проверьте, когда был создан qt и какие умные указатели были доступны в то время.
Дайний,
3

Хороший вопрос. Я не знаю конкретных статей, на которые вы ссылаетесь, но я читал подобные вещи время от времени. Я подозреваю, что авторы таких статей склонны склоняться к предвзятому отношению к программированию в стиле C ++. Если писатель программирует на C ++ только тогда, когда он должен, а затем возвращается в Java или тому подобное, как только может, тогда он на самом деле не разделяет мышление C ++.

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

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

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

Обновление: Комментатор правильно заметил, что новый C ++ unique_ptr(доступный с TR1) не считает ссылки. У комментатора также есть другое определение «умного указателя», чем я имею в виду. Он может быть прав насчет определения.

Дальнейшее обновление: тема комментариев ниже освещает. Все это рекомендуется к прочтению.

THB
источник
2
Для начала, программирование встраиваемых систем - это огромное меньшинство всего программирования, и оно совершенно не имеет значения. C ++ - это язык общего назначения. Во-вторых, shared_ptrведет подсчет ссылок. Существует много других типов интеллектуальных указателей, которые вообще не ведут подсчет ссылок. Наконец, упомянутые библиотеки нацелены на платформы, у которых есть много свободных ресурсов. Не то, чтобы я был downvoter, но все, что я говорю, - то, что Ваш пост полон неправильного.
Щенок
2
@ thb - я согласен с тобой. DeadMG - Пожалуйста, попробуйте жить без встроенных систем. Да - у некоторых умных указателей нет накладных расходов, но у некоторых есть. ОП упоминает библиотеки. Например, в Boost есть компоненты, которые используются встроенными системами, но интеллектуальные указатели могут быть неподходящими для определенных приложений.
Эд Хил
2
@EdHeal: Не жить без встроенных систем! = Программирование для них - не крошечное, не относящееся к делу меньшинство. Умные указатели подходят для любой ситуации, в которой вам нужно управлять временем жизни ресурса.
Щенок
4
shared_ptrне имеет накладных расходов. Он имеет накладные расходы только в том случае, если вам не нужна поточно-ориентированная семантика общего владения, что он и обеспечивает.
Р. Мартиньо Фернандес
1
Нет, shared_ptr имеет значительные накладные расходы по сравнению с минимумом, необходимым для поточно-ориентированной семантики общего владения; в частности, он выделяет блок кучи отдельно от фактического объекта, которым вы делитесь, с единственной целью хранения рефконта. intrusive_ptr более эффективен, но (как и shared_ptr) также предполагает, что каждый указатель на объект будет intrusive_ptr. Вы можете получить даже меньшие издержки, чем intrusive_ptr, с помощью общего указателя подсчета ссылок, как я делаю в моем приложении, и затем использовать T * всякий раз, когда вы можете гарантировать, что хотя бы один умный указатель переживет значение T *.
Qwertie
2

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

У людей могут быть самые разные требования и решения по управлению памятью, помимо умных указателей. Я мог бы захотеть управлять памятью сам, я мог бы выделить место для вещей в пуле памяти, чтобы он выделялся заранее, а не во время выполнения (полезно для игр). Возможно, я использую реализацию C ++ со сборщиком мусора (C ++ 11 делает это возможным, хотя еще не существует). Или, может быть, я просто не делаю ничего достаточно продвинутого, чтобы беспокоиться о них, я могу знать, что не забуду неинициализированные объекты и так далее. Возможно, я просто уверен в своей способности управлять памятью без использования указателя.

Интеграция с C - еще одна проблема.

Еще одна проблема - умные указатели являются частью STL. C ++ предназначен для использования без STL.

Дэвид С. Бишоп
источник
« Другая проблема заключается в том, что умные указатели являются частью STL. » Это не так.
любопытный парень
0

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

Если бы я написал приложение для форм, я мог бы подумать об использовании умных указателей; но как только управление памятью становится второй натурой, отсутствие детального контроля над памятью становится довольно раздражающим.

Гадкий Дэвис
источник
3
Нет такой вещи, как «накладные расходы на повышение».
любопытный парень
4
Я никогда не делал так, чтобы shared_ptr замедлял игровой движок до какой-либо степени. Они ускорили процесс производства и отладки. Кроме того, что именно вы подразумеваете под "издержками наддува"? Это довольно большое одеяло для броска.
derpface
@curiousguy: это накладные расходы на компиляцию всех этих заголовков и шаблон макроса + voodoo ...
einpoklum