Чому бібліотеки та фреймворки C ++ ніколи не використовують смарт-покажчики?


156

Я прочитав у кількох статтях, що сировинні покажчики майже ніколи не повинні використовуватися. Натомість вони завжди повинні бути загорнуті всередину розумних покажчиків, незалежно від того, чи це вказівки масштабу або спільні.

Однак я помітив, що такі рамки, як Qt, wxWidgets та бібліотеки, як Boost, ніколи не повертаються та не очікують розумних покажчиків, як ніби вони їх взагалі не використовують. Натомість вони повертаються або очікують сирих покажчиків. Чи є причина для цього? Чи слід триматися подалі від розумних покажчиків, коли я пишу публічний API, і чому?

Цікаво, чому розумні покажчики рекомендуються, коли багато великих проектів, схоже, уникають їх.


22
Усі ті бібліотеки, яких ви тільки що назвали, були створені багато років тому. Інтелектуальні покажчики стали справді стандартними лише для C ++ 11.
chrisaycock

22
Інтелектуальні покажчики мають накладні витрати (підрахунок посилань тощо), що може бути критично важливим, наприклад, у вбудованих системах / в режимі реального часу. IMHO - розумні покажчики призначені для ледачих програмістів. Також багато API - це найменший загальний знаменник. Я відчуваю, як полум’я лине навколо моїх ніг, коли я набираю!
Ed Heal

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. Але з огляду на це, більшість бібліотек просто експортують інтерфейс С - а це означає необроблені покажчики.

Однак, небібліотечний код повинен, як правило, віддавати перевагу розумним покажчикам над необробленими.


17
Я згоден з вами, навіть передача рядка std :: може бути болем, це багато говорить про C ++ як про "чудову мову для бібліотек".
Ha11owed

8
Підсумок більше нагадує: якщо ви хочете розповсюдити попередню версію, ви повинні зробити це для кожного компілятора, який ви хочете підтримати.
josefx

6
@josefx: Так, це сумно, але правда, єдиною альтернативою є COM або необмежений інтерфейс C. Я б хотів, щоб комітет C ++ почав турбуватися за подібні проблеми. Я маю на увазі, що це не так, як C ++ - це нова мова від 2 років тому.
Робот безлад

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
}

14
На 3. Деякі смарт-покажчики мають додаткові витрати на час / простір, інші - ні, в тому числі std::auto_ptrце вже давно є частиною стандарту (і зауважте, я люблю std::auto_ptrяк тип повернення для функцій, що створюють об'єкти, навіть якщо це майже марно скрізь ще). У C ++ 11 std::unique_ptrнемає додаткових витрат за звичайний покажчик.
Девід Родрігес - дрибес

4
Саме ... є приємна симетрія щодо появи unique_ptrта зникнення auto_ptr, для націлювання коду C ++ 03 слід використовувати пізніші, тоді як націлювання на код C ++ 11 може використовувати перше. Розумних покажчиків немає shared_ptr , є багато стандартних і жодних стандартних, включаючи пропозиції до стандарту, які були відхилені якmanaged_ptr
David Rodríguez - dribeas

2
@iammilind, це цікаві моменти, але найсмішнішим є те, що якщо ми врешті використовуємо розумні покажчики, як, мабуть, багато хто рекомендує, ми створимо код, несумісний з основними бібліотеками. Звичайно, ми можемо обернути / відкрутити розумні покажчики за потребою, але це здається великим клопотом і створило б непослідовий код (колись ми маємо справу зі смарт-покажчиками, а ніколи).
Лоран

7
Заява про те, що смарт-покажчики мають "додаткові витрати часу / простору", є дещо оманливим; всі розумні покажчики, за винятком unique_ptrвитрат на виконання, але unique_ptrна сьогоднішній день є найбільш частою. Приклад коду ви надаєте також вводить в оману, тому що unique_ptrі T*це абсолютно різні поняття. Той факт, що ви ставитесь до них обох, typeстворює враження, що їх можна замінити один на одного.
void-pointer

12
Ви не можете вводити подібний параметр, ці типи жодним чином не еквівалентні. Написання подібних типів тексту задає проблеми.
Алекс Б

35

Зі смарт-покажчиками є дві проблеми (до C ++ 11):

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

За замовчуванням смарт - покажчик, в тому , що вона є економічно вільним є unique_ptr. На жаль, для цього потрібна семантика руху C ++ 11, яка з’явилася лише нещодавно. Усі інші розумні покажчики мають вартість ( shared_ptr, intrusive_ptr) або мають меншу ідеальну семантику ( auto_ptr).

Коли C ++ 11 за кутом, приносячи a std::unique_ptr, можна було б спокусити думати, що він нарешті закінчився ... Я не такий оптимістичний.

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


12

Ви не повинні залишатися осторонь розумних покажчиків, вони використовують їх особливо в додатках, де вам потрібно передати об'єкт навколо.

Бібліотеки, як правило, або просто повертають значення або заповнюють об'єкт. Зазвичай у них немає об'єктів, які потрібно використовувати у багатьох місцях, тому немає необхідності використовувати розумні покажчики (принаймні, не в їх інтерфейсі, вони можуть використовувати їх всередині).

Я можу взяти за приклад бібліотеку, над якою ми працювали, де після декількох місяців розвитку я зрозумів, що ми використовуємо лише покажчики та розумні покажчики в кількох класах (3-5% усіх класів).

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

Редагувати (я не можу коментувати через свою репутацію): передача змінних за посиланням дуже гнучка: якщо ви хочете, щоб об’єкт читався лише тоді, ви можете використовувати посилання на const (ви все ще можете зробити деякі неприємні касти, щоб мати можливість записати об'єкт ), але ви отримуєте максимально можливий захист (це саме з розумними вказівниками). Але я згоден, що набагато приємніше просто повернути об’єкт.


З вами точно не погоджуюся, але зазначу, що існує школа думки, яка в більшості випадків знецінює передачу змінних посилань. Зізнаюся, я дотримуюся тієї школи. Я вважаю за краще функції не змінювати свої аргументи. У будь-якому випадку, наскільки я знаю, змінні посилання C ++ нічого не запобігають неправильному поводженню з об'єктами, на які вони посилаються, що саме мають намір робити розумні покажчики.
вт

2
ти маєш це для цього (мабуть, я можу коментувати: D).
робот безлад

9

Qt безглуздо перетворив багато частин бібліотеки Standard у спробі стати Java. Я вважаю, що у нас зараз є власні розумні покажчики, але взагалі це навряд чи вершина дизайну. wxWidgets, наскільки я знаю, був розроблений задовго до написання корисних розумних покажчиків.

Що стосується Boost, я повністю сподіваюся, що вони використовують розумні покажчики, де це можливо. Можливо, вам доведеться бути більш конкретними.

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


19
Qt було написано до того, як значна частина функціональних можливостей була досить широко поширена на платформах, які вона хотіла використовувати. Він давно має розумні покажчики і використовує їх для неявного обміну ресурсами майже у всіх Q * класах.
rubenvb

6
Кожна бібліотека графічного інтерфейсу без потреби відновлює колесо. Навіть рядки, Qt має QString, wxWidgets має wxString, MFC має жахливо названий CString. Чи не достатньо UTF-8 std::stringдля 99% завдань GUI?
Зворотний

10
@Inverse QString був створений, коли std :: string не було навколо.
MrFox

Перевірте, коли створено qt та які розумні покажчики були доступні на той час.
Дайній

3

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

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

Інтелектуальні покажчики чудові, але вони повинні зберігати підрахунки. Ведення довідкових підрахунків несе витрати - часто скромні витрати, але, тим не менш, витрати - під час виконання. Немає нічого поганого в тому, щоб заощадити ці витрати за допомогою голих покажчиків, особливо якщо покажчиками керують деструктори.

Однією з відмінних речей C ++ є підтримка програмування вбудованих систем. Використання голих покажчиків є частиною цього.

Оновлення: Коментатор правильно помітив, що новий C ++ unique_ptr(доступний з TR1) не враховує посилання. У коментатора також є інше визначення "розумного вказівника", ніж я маю на увазі. Він може мати рацію щодо визначення.

Подальше оновлення: Низка коментарів нижче висвітлюється. Все це рекомендується прочитати.


2
Для початку програмування вбудованих систем - це величезна меншість усіх програмувань, і зовсім не має значення. C ++ - мова загального призначення. По-друге, shared_ptrведе облік довідок Існує багато інших типів розумних покажчиків, які взагалі не зберігають посилання. Нарешті, зазначені бібліотеки орієнтовані на платформи, у яких є достатньо ресурсів. Не те, щоб я був низовином, але все, що я говорю, - це те, що ваша посада повна неправильних.
Щеня

2
@thb - я згоден з вашими настроями. DeadMG - Будь ласка, спробуйте жити без вбудованих систем. Так - деякі розумні покажчики не мають накладних витрат, але деякі -. В ОП згадуються бібліотеки. Наприклад, у Boost є частини, які використовуються вбудованими системами, але розумні покажчики можуть бути невідповідними для певних програм.
Ed Heal

2
@EdHeal: Не жити без вбудованих систем! = Програмування для них - це не крихітна, неактуальна меншість. Розумні покажчики підходять для будь-якої ситуації, в якій потрібно керувати ресурсом ресурсу.
Щеня

4
shared_ptrне має накладних витрат. Він має накладні витрати лише тоді, коли вам не потрібна безпечна для потоків семантика власності, що саме вона пропонує.
Р. Мартіньо Фернандес

1
Ні, shared_ptr має значні накладні витрати над мінімумом, необхідним для безпечної потокової семантики власності; конкретно, він виділяє блок купи окремо від фактичного об'єкта, яким ви ділитесь, з єдиною метою зберігання знижки. intrusive_ptr є більш ефективним, але (як і спільний_ptr) він також передбачає, що кожен покажчик на об'єкт буде intrusive_ptr. Ви можете отримати навіть нижчі накладні витрати, ніж intrusive_ptr, за допомогою спеціального загального підрахунку загального підрахунку, як я роблю в моєму додатку, а потім використовувати T *, коли ви можете гарантувати, що принаймні один розумний покажчик переживе значення T *
Qwertie

2

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

Люди можуть мати різноманітні вимоги та рішення щодо управління пам’яттю за межами розумних покажчиків. Я, можливо, захочу самостійно керувати пам’яттю, я можу виділити місця для речей у пулі пам’яті, щоб це було виділено заздалегідь, а не під час виконання (корисно для ігор). Я можу використовувати сміття зібраною реалізацією C ++ (C ++ 11 робить це можливим, хоча ще не існує). А може, я просто не роблю нічого достатньо розвиненого, щоб переживати, що турбуватися з ними, я можу знати, що не забуду неініціалізовані об’єкти тощо. Можливо, я просто впевнений у своїй здатності керувати пам’яттю без миші вказівника.

Інтеграція з C також є іншим питанням.

Інша проблема - розумні покажчики є частиною STL. C ++ призначений для використання без STL.


" Інша проблема - розумні покажчики є частиною STL ". Вони не є.
curiousguy

0

Це також залежить від того, в якому домені ви працюєте. Я пишу ігрові двигуни для життя, ми уникаємо прискорення, як чума, в іграх накладний приріст неприйнятний. У нашому основному двигуні ми закінчили писати власну версію stl (Начебто ea stl).

Якби я писав додаток для форм, я міг би розглянути можливість використання розумних покажчиків; але як тільки управління пам'яттю є другою природою, не має чіткого контролю над пам'яттю, стає тихо дратує.


3
Немає такого поняття, як "накладні зусилля".
curiousguy

4
Я ніколи не мав share_ptr сповільнювати свій ігровий движок на будь-яку увагу. Однак вони прискорили процес виробництва та налагодження. Крім того, що саме ви маєте на увазі під «накладними витратами»? Це досить велика ковдра для лиття.
derpface

@curiousguy: Це компіляція всіх цих заголовків і макрос + шаблону вуду ...
einpoklum
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.