Чи слід зберігати цілі об'єкти чи покажчики на об’єкти в контейнерах?


162

Проектування нової системи з нуля. Я буду використовувати STL для зберігання списків і карт певних довгоживучих об'єктів.

Запитання: Чи повинен я забезпечити, щоб мої об'єкти мали конструктори копій і зберігали копії об'єктів в моїх контейнерах STL, або як правило, краще керувати життям і сферою застосування і просто зберігати вказівники на ці об’єкти в моїх контейнерах STL?

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

Два дуже очевидних недоліки для гри з покажчиками: 1) Я повинен сам керувати розподілом / розстановкою цих об'єктів у межах, що перевищують STL. 2) Я не можу створити об’єкт temp на стеку і додати його до своїх контейнерів.

Чи ще чогось мені не вистачає?


36
Боже, я люблю цей сайт, це ТОЧНЕ запитання, про яке я думав сьогодні ... дякую, що виконав роботу, задавши його мені :-)
eviljack

2
Ще одна цікава річ полягає в тому, що ми повинні перевірити, чи дійсно вказівник був доданий до колекції, і якщо він не є, ми, ймовірно, повинні викликати видалити, щоб уникнути витоку пам'яті ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered

Відповіді:


68

Оскільки люди підкреслюють ефективність використання покажчиків.

Якщо ви плануєте використовувати std :: vector і якщо оновлень небагато, і ви часто повторюєте свою колекцію, і це об'єкт, який зберігає неполіморфний тип, "копії" будуть більш ефективними, оскільки ви отримаєте кращу локальність посилання.

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


7
З точки зору локальності кешу, зберігання покажчиків у вектор може бути ефективним, якщо його використовувати разом із спеціальним розподільником для pointees. Спеціальний розподільник повинен дбати про місце кешу, наприклад, використовуючи нове розміщення (див. En.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
Аміт

47

Це дійсно залежить від вашої ситуації.

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

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

Якщо ви вирішили використовувати вказівники для об’єктів, погляньте на бібліотеку контейнерів Boost Pointer Container . Ця бібліотека прискорення охоплює всі контейнери STL для використання з динамічно виділеними об'єктами.

Кожен контейнер-вказівник (наприклад, ptr_vector) приймає право власності на об'єкт, коли він додається до контейнера, і керує терміном експлуатації цих об'єктів для вас. Ви також отримуєте доступ до всіх елементів контейнера ptr_ за посиланням. Це дозволяє робити такі речі

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Ці класи обгортають контейнери STL та працюють з усіма алгоритмами STL, що дуже зручно.

Також є засоби для передачі права власності на вказівник у контейнері абоненту (через функцію випуску у більшості контейнерів).


38

Якщо ви зберігаєте полімфорні об'єкти, вам завжди потрібно використовувати колекцію вказівників базового класу.

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


1
Я любив нарізку дімона!
idichekop

22

Вибачте, що стрибаю через 3 роки після події, але тут застережлива примітка ...

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

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

Для мене мораль у будь-якому разі: якщо ваша специфікація не на 100% відлита в камінь, займіться покажчиками, і ви, можливо, пізніше заощадите багато роботи.


Технічні характеристики ніколи не встановлюються в камені. Я не думаю, що це означає, що ви повинні використовувати виключно контейнери для покажчиків, хоча контейнери Boost покажчиків здають цей варіант набагато привабливішим. Я скептично налаштований переробити всю програму відразу, якщо ви вирішите перетворити контейнер об'єкта в контейнер вказівника. Це може бути справа в деяких проектах. У цьому випадку це крихка конструкція. У такому випадку не звинувачуйте свою проблему в "слабкості" контейнерів для об'єктів.
allyourcode

Ви могли залишити елемент у векторі зі значенням семантики і зробили поліморфну ​​поведінку всередині.
Біллі ONeal

19

Чому б не отримати найкраще з обох світів: зробіть контейнер розумних покажчиків (наприклад, boost::shared_ptrабо std::shared_ptr). Вам не потрібно керувати пам'яттю, і вам не доведеться мати справу з великими операціями копіювання.


Чим цей підхід відрізняється від запропонованого Ніком Хаддадом, використовуючи бібліотеку Boost Pointer Container?
Торстен Шьонінг

10
@ ThorstenSchöning std :: shared_ptr не додає залежності від підвищення.
Джеймс Джонстон

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

11

Зазвичай зберігання об'єктів безпосередньо в контейнері STL найкраще, оскільки воно є найпростішим, найефективнішим та найпростішим для використання об'єкта.

Якщо ваш об’єкт має не копіюється синтаксис або є абстрактним базовим типом, вам потрібно буде зберігати покажчики (найпростіше - використовувати shared_ptr)


4
Це не найефективніше, якщо ваші об’єкти великі, і ви часто переміщаєте елементи.
allyourcode

3

Ви, здається, добре розумієте різницю. Якщо об’єкти невеликі і їх легко копіювати, то їх обов'язково зберігайте.

Якщо ні, то я б подумав про збереження розумних покажчиків (не auto_ptr, посилання підрахунку розумних покажчиків) на ті, які ви виділяєте на купі. Очевидно, якщо ви вибрали розумні покажчики, ви не можете зберігати тимчасові стеки виділених об'єктів (як ви вже говорили).

@ Torbjörn робить хороший момент щодо нарізки.


1
О, і ніколи ніколи не створюйте колекцію auto_ptr's
Torbjörn Gyllebring

Правильно, auto_ptr не є розумним покажчиком - він не позначає.
Лу Франко,

auto_ptr також не має неруйнівної семантики копіювання. Акт присвоєння auto_ptr від oneдо anotherбуде звільняти посилання oneта змінюватись one.
Енді Фінкенштадт

3

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

Тут є корисна інформація про контейнери STL та смарт-покажчики:

Чому неправильно використовувати std :: auto_ptr <> зі стандартними контейнерами?


2

Якщо об'єкти мають бути передані в іншому місці коду, зберігайте у векторному boost :: shared_ptr. Це гарантує, що покажчики на об’єкт залишаться дійсними, якщо змінити розмір вектора.

Тобто:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

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

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

1

Це питання мене клопоче вже деякий час.

Я схиляюся до зберігання покажчиків, але у мене є деякі додаткові вимоги (обшивки SWIG lua), які можуть не стосуватися вас.

Найважливіший пункт у цій публікації - це протестувати його самостійно , використовуючи свої об’єкти

Я зробив це сьогодні, щоб перевірити швидкість виклику функції члена на колекції з 10 мільйонів об'єктів, в 500 разів.

Функція оновлює x і y на основі xdir та ydir (усі змінні елемента з плаваючим членом).

Я використовував std :: list для зберігання обох типів об'єктів, і я виявив, що зберігання об'єкта в списку відбувається трохи швидше, ніж використання вказівника. З іншого боку, продуктивність була дуже близькою, тому зводиться до того, як вони будуть використовуватися у вашій програмі.

Для довідки, з -O3 на моєму апаратному забезпеченні покажчики потребували 41 секунди, а для необмежених об'єктів - 30 секунд.

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