Чому б не використовувати покажчики для всього в C ++?


75

Припустимо, що я визначив якийсь клас:

class Pixel {
    public:
      Pixel(){ x=0; y=0;};
      int x;
      int y;
}

Потім напишіть код, використовуючи його. Чому я повинен робити наступне?

Pixel p;
p.x = 2;
p.y = 5;

Виходячи зі світу Java, я завжди пишу:

Pixel* p = new Pixel();
p->x = 2;
p->y = 5;

Вони в основному роблять одне і те ж, так? Один знаходиться у стеку, а інший - у купі, тому пізніше мені доведеться його видалити. Чи існує якась принципова різниця між ними? Чому я повинен віддавати перевагу одному перед іншим?

Відповіді:


188

Так, один на стосі, інший на купі. Є дві важливі відмінності:

  • По-перше, очевидний і менш важливий: розподіл купи відбувається повільно. Розподіл стеків відбувається швидко.
  • По-друге, і набагато важливішим є RAII . Оскільки версія, виділена стеком, автоматично очищається, це корисно . Його деструктор викликається автоматично, що дозволяє гарантувати очищення будь-яких ресурсів, виділених класом. Це дуже важливо, як уникнути витоків пам'яті в C ++. Ви уникаєте їх, ніколи не викликаючи deleteсебе, а замість цього обертаючи їх виділеними стеком об'єктами, які викликають deleteвнутрішньо, типово у своєму деструкторі. Якщо ви намагаєтеся вручну відстежувати всі розподіли та дзвонити deleteв потрібний час, я гарантую вам, що у вас буде принаймні витік пам'яті на 100 рядків коду.

Як невеликий приклад розглянемо цей код:

class Pixel {
public:
  Pixel(){ x=0; y=0;};
  int x;
  int y;
};

void foo() {
  Pixel* p = new Pixel();
  p->x = 2;
  p->y = 5;

  bar();

  delete p;
}

Досить невинний код, так? Ми створюємо піксель, потім викликаємо якусь не пов’язану функцію, а потім видаляємо піксель. Чи є витік пам'яті?

І відповідь - "можливо". Що станеться, якщо barвидається виняток? deleteніколи не викликається, піксель ніколи не видаляється, і ми втрачаємо пам’ять. Тепер розглянемо це:

void foo() {
  Pixel p;
  p.x = 2;
  p.y = 5;

  bar();
}

Це не призведе до витоку пам’яті. Звичайно, у цьому простому випадку все знаходиться у стеці, тому воно очищається автоматично, але навіть якщо Pixelклас внутрішньо зробив динамічне розподіл, це також не витіче. PixelКлас буде просто дати деструктор , який видаляє його, і цей деструктор не називатиме , незалежно від того , як ми виходимо з fooфункції. Навіть якщо ми залишимо це, тому що barкинули виняток. Наступний, трохи надуманий приклад показує це:

class Pixel {
public:
  Pixel(){ x=new int(0); y=new int(0);};
  int* x;
  int* y;

  ~Pixel() {
    delete x;
    delete y;
  }
};

void foo() {
  Pixel p;
  *p.x = 2;
  *p.y = 5;

  bar();
}

Клас Pixel тепер внутрішньо виділяє деяку пам’ять купи, але його деструктор дбає про її очищення, тому, використовуючи клас, нам не доведеться про це турбуватися. (Мені, мабуть, слід згадати, що останній приклад тут значно спрощений, щоб показати загальний принцип. Якби насправді використовувати цей клас, він також містить кілька можливих помилок. Якщо розподіл y не вдається, x ніколи не звільняється , і якщо Pixel копіюється, ми в кінцевому підсумку обидва екземпляри намагаємось видалити однакові дані. Тож візьміть остаточний приклад тут із невеликою кількістю солі. Реальний код трохи складніший, але він показує загальну ідею)

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


5
+1. Хоча 1leak / 100loc - це занадто багато. Можливо, 1 на 1000 рядків коду.
Мілан Бабушков

10
@Milan: В умовах винятків я б сказав, що 100, напевно, ближче, ніж 1000.
mghie

5
Так, ви, мабуть, зможете написати перші 500 рядків без витоків. А потім ви додаєте ще 100 рядків, які містять 6 різних способів витоку одних і тих самих даних, все в одній функції. Звичайно, я цього не вимірював, але це звучало добре. :)
jalf

3
@Matt: о справді? Вам не потрібно турбуватися про управління пам’яттю, якщо ви не використовуєте винятки? Це новина для мене. Думаю, велика кількість програмістів на С бажають, щоб і вони про це знали. Я вважаю, що багато великих програмних проектів, написаних на мові C, можна було б значно спростити, якби тільки вони знали цей маленький самородок мудрості: що поки немає винятків, не потрібно керувати своєю пам’яттю.
jalf

3
@Matt: Я ні. Я навмисно їх інтерпретую. Тут немає "помилок". Дивлячись на ряд коментарів, які ви залишаєте до всіх моїх відповідей, стає цілком зрозумілим, скільки вони варті. У будь-якому випадку, я не бачу жодного "нав'язливого шаблону" у своєму дописі. Також я не бачу нічого, що призначене для захисту від функцій. Я бачу, як дуже проста ідіома використовується для написання дуже простого коду, який дуже простий у використанні. Без цього клієнтський код став би більш складним і більш крихким, а реалізація самого класу заощадила б, можливо, кілька рядків коду.
jalf

30

Вони не однакові, доки ви не додасте видалення.
Ваш приклад надто тривіальний, але деструктор може насправді містити код, який виконує якусь реальну роботу. Це називається RAII.

Тож додайте видалення. Переконайтеся, що це відбувається навіть тоді, коли поширюються винятки.

Pixel* p = NULL; // Must do this. Otherwise new may throw and then
                 // you would be attempting to delete an invalid pointer.
try
{
    p = new Pixel(); 
    p->x = 2;
    p->y = 5;

    // Do Work
    delete p;
}
catch(...)
{
    delete p;
    throw;
}

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

File file;
try
{
    file = new File("Plop");
    // Do work with file.
}
finally
{
    try
    {
        file.close();     // Make sure the file handle is closed.
                          // Oherwise the resource will be leaked until
                          // eventual Garbage collection.
    }
    catch(Exception e) {};// Need the extra try catch to catch and discard
                          // Irrelevant exceptions. 

    // Note it is bad practice to allow exceptions to escape a finally block.
    // If they do and there is already an exception propagating you loose the
    // the original exception, which probably has more relevant information
    // about the problem.
}

Той самий код у C ++

std::fstream  file("Plop");
// Do work with file.

// Destructor automatically closes file and discards irrelevant exceptions.

Хоча люди згадують про швидкість (через пошук / розподіл пам'яті в купі). Особисто це не є вирішальним фактором для мене (розподільники дуже швидкі і оптимізовані для використання C ++ невеликих об'єктів, які постійно створюються / руйнуються).

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

Основна відмінність між C ++ і Java полягає в:

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

Прикладами є:

 std::auto_ptr<Pixel>   p(new Pixel);
 // An auto_ptr has move semantics.
 // When you pass an auto_ptr to a method you are saying here take this. You own it.
 // Delete it when you are finished. If the receiver takes ownership it usually saves
 // it in another auto_ptr and the destructor does the actual dirty work of the delete.
 // If the receiver does not take ownership it is usually deleted.

 std::tr1::shared_ptr<Pixel> p(new Pixel); // aka boost::shared_ptr
 // A shared ptr has shared ownership.
 // This means it can have multiple owners each using the object simultaneously.
 // As each owner finished with it the shared_ptr decrements the ref count and 
 // when it reaches zero the objects is destroyed.

 boost::scoped_ptr<Pixel>  p(new Pixel);
 // Makes it act like a normal stack variable.
 // Ownership is not transferable.

Є й інші.


9
Мені подобається порівнювати використання файлів C ++ із Java (викликає посмішку).
Мартін Йорк,

2
погодився. І бонусні бали, оскільки він показує, що RAII використовується для управління іншими типами ресурсів, а не просто розподілом пам'яті.
jalf

25

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

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

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

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


24

Я вважаю за краще використовувати перший метод, коли у мене є можливість, тому що:

  • це швидше
  • Мені не потрібно турбуватися про вивільнення пам'яті
  • p буде дійсним об'єктом для всієї поточної області дії

14

"Чому б не використовувати покажчики для всього в C ++"

Одна проста відповідь - оскільки це стає величезною проблемою управління пам'яттю - розподілу та видалення / звільнення.

Автоматичні / стекові об'єкти усувають частину напруженої роботи.

це лише перше, що я сказав би з цього питання.


11

Хороше загальне емпіричне правило - НІКОЛИ не використовувати нове, якщо вам цього не потрібно. Ваші програми будуть простішими в обслуговуванні і менше схильні до помилок, якщо ви не будете використовувати нові, оскільки вам не доведеться турбуватися про те, де їх очистити.


11

Код:

Pixel p;
p.x = 2;
p.y = 5;

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

Використання new вимагає всього цього накладного управління пам’яттю.

Потім виникає запитання - чи потрібно використовувати для даних дані простір стека чи кучу. Стек (або локальні) змінні, такі як 'p', не вимагають перенаправлення посилань, тоді як використання нових додає шар опосередкованості.


10

Так, спочатку це має сенс, виходячи з фону Java або C #. Не здається великою справою, коли доводиться пам’ятати, щоб звільнити виділену пам’ять. Але тоді, коли ви отримаєте перший витік пам’яті, ви почухаєте собі голову, бо КЛИВАЛИсь, що звільнили все. Тоді вдруге це станеться, а в третій ви ще більше засмутитесь. Нарешті, після шести місяців головного болю через проблеми з пам’яттю ви почнете втомлюватися від цього, і ця виділена стеком пам’ять почне виглядати дедалі привабливішою. Як гарно і чисто - просто покладіть його на стопку і забудьте про це. Досить скоро ви будете використовувати стек у будь-який час, коли зможете з цим позбутися.

Але - цього досвіду немає заміни. Моя порада? Спробуй по-своєму, поки що. Ви побачите.


6
Ви забули згадати його злого близнюка, подвійні звільнення. :) Просто коли ви думаєте, що звільнили всю свою пам’ять, ви починаєте отримувати помилки, оскільки використовуєте пам’ять після її звільнення, або намагаєтеся звільнити вже звільнену пам’ять.
jalf

6

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

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


6

Найкраща причина не все нове - це те, що ви можете дуже детерміновано провести очищення, коли речі перебувають у стосі. У випадку з Pixel це не настільки очевидно, але у випадку скажімо з файлом це стає вигідним:

  {   // block of code that uses file
      File aFile("file.txt");
      ...
  }    // File destructor fires when file goes out of scope, closing the file
  aFile // can't access outside of scope (compiler error)

У разі оновлення файлу вам доведеться пам’ятати про його видалення, щоб отримати однакову поведінку. У наведеному випадку це здається простим питанням. Однак розглянемо більш складний код, наприклад, зберігання покажчиків у структурі даних. Що робити, якщо ви передасте цю структуру даних іншому фрагменту коду? Хто відповідає за прибирання. Хто б закрив усі ваші файли?

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

Ця концепція відома як RAII - розподіл ресурсів - це ініціалізація, і це може суттєво покращити вашу здатність мати справу із придбанням та розпорядженням ресурсами.


6

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

class Rectangle {
    Pixel top_left;
    Pixel bottom_right;
}

Rectangle r1; // Pixel is allocated on the stack
Rectangle *r2 = new Rectangle(); // Pixel is allocated on the heap

Основними перевагами стекових змінних є:

  • Ви можете використовувати шаблон RAII для управління об’єктами. Як тільки об'єкт виходить за межі сфери дії, викликається його деструктор. Начебто як шаблон "з використанням" у C #, але автоматичний.
  • Немає можливості нульового посилання.
  • Вам не потрібно турбуватися про ручне керування пам’яттю об’єкта.
  • Це спричиняє менше виділення пам'яті. Розподіл пам'яті, особливо невеликих, у C ++, швидше за все, буде повільнішим, ніж Java.

Після створення об’єкта немає різниці в продуктивності між об’єктом, виділеним у купі, та об’єктом, виділеним у стеку (або де завгодно).

Однак ви не можете використовувати будь-який вид поліморфізму, якщо не використовуєте покажчик - об’єкт має повністю статичний тип, який визначається під час компіляції.


4

Термін служби об’єкта. Якщо ви хочете, щоб час життя вашого об'єкта перевищував час поточного обсягу, ви повинні використовувати купу.

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


4

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

Оскільки вам доводиться запитувати себе "Що станеться, якщо цей параметр дорівнює нулю?", Вам доведеться кодувати більш захисно, постійно дбаючи про нульові перевірки. Це говорить про використання посилань.

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


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

Я думаю, ви відповідаєте на інше запитання щодо покажчиків проти посилань; а не питання OP про об'єкти на основі стеку або купи.
saw-lau

4

Проблема полягає не в вказівниках як таких (крім введення NULLвказівників), а в управлінні пам’яттю вручну.

Звичайно, кумедна частина полягає в тому, що кожен підручник з Java, який я бачив, згадував про збирач сміття, - це така крута гарячість, тому що вам не потрібно пам’ятати про дзвінок delete, коли на практиці C ++ вимагає лише deleteдзвінка newdelete[]коли ви телефонуєте new[]).


2

Використовуйте покажчики та динамічно розподілені об’єкти ТІЛЬКИ ТОГО, ЩО ПОВИННЕ Використовуйте статично розміщені (глобальні або стекові) об’єкти, де це можливо.

  • Статичні об'єкти швидші (відсутність нових / видалення, непрямий доступ до них)
  • Жодного предмета, про який слід турбуватися
  • Менше натискань клавіш Більш читабельно
  • Набагато міцніший. Кожне "->" - це потенційний доступ до NIL або недійсна пам'ять

Щоб пояснити, під "статичним" у цьому контексті я маю на увазі нединамічно розподілений. IOW, нічого НЕ на купі. Так, вони також можуть мати проблеми з життям об’єктів - з точки зору порядку знищення одиночного, - але наклеювання їх на купу зазвичай нічого не вирішує.


Не можу сказати, що мені подобається "статична" порада. По-перше, це не вирішує проблему (оскільки статичні об'єкти не можуть бути виділені під час виконання), а по-друге, у них є безліч власних проблем (наприклад, безпека потоків). Тим не менше, я не -1.
jalf

Ви також повинні зауважити, що статика має проблеми як із запуском, так і з зупинкою (google для "фіаско порядку статичної ініціалізації"). Тим не менш, я теж не -1. Тож не робіть мені нічого, будь ласка! :)
Йоханнес Шауб - літб

1
@Roddy - Ви мали на увазі "автоматичний" (виділений стеком) замість "статичний"? (І я теж не -1.)
Фред Ларсон,

@ jalf- можливо "статичний" був не найкращим словом. Ви замислюєтесь над проблемою одностороннього блокування конструкції з декількох потоків?
Родді

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

2

Чому б не використовувати покажчики для всього?

Вони повільніші.

Оптимізація компілятора не буде настільки ефективною при симантиці доступу до покажчиків, ви можете прочитати про це на будь-якій кількості веб-сайтів, але ось гідний pdf від Intel.

Чекові сторінки, 13,14,17,28,32,36;

Виявлення непотрібних посилань на пам'ять у позначенні циклу:

for (i = j + 1; i <= *n; ++i) { 
X(i) -= temp * AP(k); } 

Позначення меж циклу містить покажчик або посилання на пам'ять. Компілятор не має засобів передбачити, чи змінюється значення, на яке посилається вказівник n, за допомогою ітерацій циклу якимось іншим призначенням. Це використовує цикл для перезавантаження значення, на яке посилається n для кожної ітерації. Механізм генератора коду також може заборонити планування конвеєрного контуру програмного забезпечення, коли знайдено потенційне згладжування покажчика. Оскільки значення, на яке посилається вказівник n, не змушує старіти всередині циклу і воно інваріантне до індексу циклу, навантаження * ns повинно переноситися за межі циклу для більш простого планування та неоднозначності вказівника.

... ряд варіацій на цю тему ....

Складні посилання на пам’ять. Або іншими словами, аналіз посилань, таких як складні обчислення покажчиків, напружує здатність компіляторів генерувати ефективний код. Місця в коді, де компілятор або апаратне забезпечення виконують складні обчислення, щоб визначити, де знаходяться дані, повинні бути в центрі уваги. Згладжування вказівника та спрощення коду допомагають компілятору розпізнавати шаблони доступу до пам'яті, дозволяючи компілятору перекривати доступ до пам'яті за допомогою маніпуляцій з даними. Скорочення непотрібних посилань на пам’ять може поставити компілятору можливість конвеєрувати програмне забезпечення. Багато інших властивостей розташування даних, таких як псевдоніми або вирівнювання, можна легко розпізнати, якщо обчислення посилань на пам'ять не залишаються простими.


Посилання збочило. :-(
derM

1

Дивлячись на питання з іншого боку ...

У C ++ ви можете посилатися на об'єкти за допомогою покажчиків ( Foo *) та посилань ( Foo &). Де тільки можливо, я використовую посилання замість вказівника. Наприклад, при передачі посилання на функцію / метод, використання посилань дозволяє коду (сподіваємось) зробити такі припущення:

  • Об'єкт, на який посилається, не належить функції / методу, отже, не повинен бути deleteоб'єктом. Це все одно, що сказати: "Ось, використовуй ці дані, але повертай їх, коли закінчиш".
  • Посилання на покажчик NULL менш вірогідні. Можна передати посилання NULL, але принаймні це не буде виною функції / методу. Посилання не може бути перепризначене новій адресі вказівника, тому ваш код не міг випадково перепризначити його NULL або якійсь іншій невірній адресі вказівника, що спричинило помилку сторінки.

1

Питання в тому, чому б ви використовували покажчики для всього? Виділені стекові об’єкти не тільки безпечніше і швидше створюються, але ще менше набирається текст, а код виглядає краще.


0

Щось, про що я не бачив, - це збільшення використання пам'яті. Припускаючи 4 байтові цілі числа та покажчики

Pixel p;

буде використовувати 8 байт, і

Pixel* p = new Pixel();

буде використовувати 12 байт, що на 50% більше. Це не звучить багато, поки ви не виділите достатньо для зображення 512x512. Тоді ви говорите 2 МБ замість 3 МБ. Це ігнорування накладних витрат на управління купою з усіма цими об’єктами на них.


0

Об'єкти, створені в стеку, створюються швидше, ніж виділені об'єкти.

Чому?

Оскільки розподіл пам'яті (за допомогою менеджера пам'яті за замовчуванням) займає певний час (щоб знайти якийсь порожній блок або навіть виділити цей блок).

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

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

Я сам би не ускладнював проблему за допомогою розумних покажчиків.

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

Отже, це питання вибору та обмежень, відповіді на них усіх немає.

І, як завжди, не забувайте робити це якомога простіше .


0

В основному, коли ви використовуєте сирі покажчики, у вас немає RAII.


0

Це мене дуже бентежило, коли я був новим програмістом на C ++ (і це була моя перша мова). Є багато дуже поганих підручників з C ++, які, як правило, потрапляють в одну з двох категорій: підручники "C / C ++", що насправді означає, що це підручник з C (можливо, з класами), і підручники з C ++, які вважають, що C ++ - це Java з видаленням .

Я думаю, мені знадобилося приблизно 1 - 1,5 року (принаймні), щоб набрати "нове" в будь-якому місці мого коду. Я часто використовував контейнери STL, подібні до векторних, які про мене подбали.

Думаю, багато відповідей або ігнорують, або просто уникають прямого сказання, як цього уникнути. Як правило, вам не потрібно виділяти за допомогою new у конструкторі та очищати за допомогою delete у деструкторі. Натомість ви можете просто вставити сам об'єкт у клас (а не вказівник на нього) та ініціалізувати сам об'єкт у конструкторі. Тоді конструктор за замовчуванням робить усе, що вам потрібно в більшості випадків.

Практично в будь-якій ситуації, коли це не спрацює (наприклад, якщо ви ризикуєте не вистачити місця в стеку), ви, мабуть, все одно повинні використовувати один із стандартних контейнерів: std :: string, std :: vector та std :: map - це три, які я використовую найчастіше, але std :: deque і std :: list також досить поширені. Інші (такі як std :: set та нестандартна мотузка ) використовуються не так часто, але поводяться подібним чином. Усі вони розподіляються з безкоштовного магазину (мова C ++ для "купи" іншими мовами), див .: C ++ STL питання: розподільники


-2

Перший варіант найкращий, якщо до класу Pixel не додано більше учасників. Оскільки все більше і більше членів додається, існує можливість виключення переповнення стека


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