Як ініціалізувати пам'ять з новим оператором на C ++?


176

Я тільки починаю вникати в C ++ і хочу підхопити деякі хороші звички. Якщо я щойно виділив тип масиву intз newоператором, то як я можу їх ініціалізувати на 0, не перебираючи їх усі? Чи варто просто використовувати memset? Чи існує спосіб "C ++"?


19
Якщо ви хочете отримати хорошу звичку C ++, то уникайте використання масивів безпосередньо та використовуйте натомість вектор. Vector буде ініціалізувати всі елементи незалежно від типу, і тоді вам не потрібно пам'ятати, щоб зателефонувати оператору delete [].
brianegge

@brianegge: Що робити, якщо мені потрібно передати масив зовнішній функції С, чи можу я просто надати йому вектор?
dreamlax

12
Можна пройти &vector[0].
jamesdlin

Звичайно, коли ви передаєте масиви функціям C, вам зазвичай потрібно вказати вказівник на перший елемент, & vector [0], як сказав @jamesdlin, і розмір масиву, що надається в цьому випадку вектор.size ().
Trebor Rude

Пов'язане (запитує типи без масиву): stackoverflow.com/questions/7546620/…
Aconcagua

Відповіді:


392

Це напрочуд маловідома особливість C ++ (про що свідчить той факт, що ніхто ще не дав це як відповідь), але він насправді має спеціальний синтаксис для ініціалізації значення масиву:

new int[10]();

Зауважте, що ви повинні використовувати порожні дужки - наприклад, ви не можете використовувати (0)чи що-небудь інше (саме тому це корисно лише для ініціалізації значень).

Це чітко дозволено ISO C ++ 03 5.3.4 [expr.new] / 15, де зазначено:

Новий вираз, який створює об'єкт типу, Tініціалізує цей об’єкт наступним чином:

...

  • Якщо новий ініціалізатор має форму (), елемент ініціалізується за значенням (8.5);

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


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

2
@Jerry: Я мушу визнати, що я ще не знав цього (можливо, тому, що коли я перейшов до читання стандарту, це було вже C ++ 03). Однак це чудово, що всі реалізації, які я знаю, підтримують це (я думаю, це тому, що це так банально реалізувати).
Павло Мінаєв

2
Так, це досить тривіально втілити в життя. Наскільки це було новим, усі "ініціалізації значення" були новими в C ++ 03.
Джеррі Коффін

34
У C ++ 11 можна використовувати рівномірний initializtion , а також: new int[10] {}. Ви також можете надати значення для ініціалізації:new int[10] {1,2,3}
bames53

Будь ласка, не плутайте ініціалізовані за замовчуванням з ініціалізованими значеннями: вони обидва чітко визначені у стандарті та є різними ініціалізаціями.
Дедуплікатор

25

Якщо припустити, що ви дійсно хочете масив, а не std :: вектор, "C ++ шлях" був би таким

#include <algorithm> 

int* array = new int[n]; // Assuming "n" is a pre-existing variable

std::fill_n(array, n, 0); 

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


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

4
Ви можете бути здивовані. Я був. У моєму STL (і GCC, і Dinkumware), std :: copy фактично перетворюється на memcpy, якщо він виявляє, що він викликається з вбудованими типами. Я би не здивувався, якби std :: fill_n використовував memset.
Брайан Ніл

2
Ні. Використовуйте "Value-Initialization", щоб встановити всі члени на 0.
Martin York

24

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

Ручна ініціалізація всіх елементів у циклі

int* p = new int[10];
for (int i = 0; i < 10; i++)
{
    p[i] = 0;
}

Використання std::memsetфункції від<cstring>

int* p = new int[10];
std::memset(p, 0, sizeof(int) * 10);

Використання std::fill_nалгоритму від<algorithm>

int* p = new int[10];
std::fill_n(p, 10, 0);

Використання std::vectorконтейнера

std::vector<int> v(10); // elements zero'ed

Якщо доступний C ++ 0x , використовуючи функції списку ініціалізатора

int a[] = { 1, 2, 3 }; // 3-element static size array
vector<int> v = { 1, 2, 3 }; // 3-element array but vector is resizeable in runtime

1
повинен бути векторний <int> Якщо ви додали p = new int [10] (), у вас був повний список.
Карстен

@mloskot, у першому випадку, коли ви ініціалізували масив за допомогою "new", як пройде посилання за посиланням? Якщо я використовував int array[SIZE] ={1,2,3,4,5,6,7};позначення, я можу використовувати void rotateArray(int (& input)[SIZE], unsigned int k);моє оголошення про функцію, що було б при використанні першої конвенції? будь-яка пропозиція?
Ану

1
Я боюся, що приклад з std::memsetневірним - ви пройдете 10, здається, очікуєте кількість байтів - див. En.cppreference.com/w/cpp/string/byte/memset . (Я думаю, що це добре показує, чому варто уникати такої конструкції низького рівня, коли це можливо.)
Сума

@Suma Чудовий улов! Виправлено. Це, здається, є кандидатом на десятирічну помилку :-) Так, я згоден з вашим коментарем.
млоскот

7

Якщо пам'ять, яку ви виділяєте, - це клас з конструктором, який робить щось корисне, оператор new зателефонує цьому конструктору і залишить ваш об'єкт ініціалізованим.

Але якщо ви виділяєте POD або щось, що не має конструктора, який ініціалізує стан об'єкта, ви не можете виділити пам'ять та ініціалізувати цю пам'ять з новим оператором за одну операцію. Однак у вас є кілька варіантів:

1) Замість цього використовуйте змінну стека. Ви можете виділити та ініціалізувати за замовчуванням за один крок, як це:

int vals[100] = {0};  // first element is a matter of style

2) використання memset(). Зауважте, що якщо об'єкт, який ви виділяєте, не є ПОД , memsetting це погана ідея. Одним із конкретних прикладів є те, що якщо ви запам’ятали клас, у якого є віртуальні функції, ви підірвете vtable і залишите об’єкт у непридатному стані.

3) У багатьох операційних системах є дзвінки, які роблять те, що ви хочете - виділяйте на купі та ініціалізуйте дані на щось. Приклад Windows може бутиVirtualAlloc()

4) Зазвичай це найкращий варіант. Уникайте взагалі керування пам’яттю. Ви можете використовувати контейнери STL для того, щоб робити майже все, що ви зробили б із необробленою пам'яттю, включаючи розподілення та ініціалізацію всіх одним махом:

std::vector<int> myInts(100, 0);  // creates a vector of 100 ints, all set to zero

6

Так, є:

std::vector<int> vec(SIZE, 0);

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

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

Тепер із C ++ 11 також є std :: масив, який моделює масив постійного розміру (проти вектора, який здатний рости). Існує також std :: unique_ptr, який управляє динамічно розподіленим масивом (який може поєднуватися з ініціалізацією, як відповіли в інших відповідях на це питання). Будь-який із них - це більше C ++ спосіб, ніж обробка вказівника на масив IMHO вручну.


11
це насправді не відповідає на поставлене питання.
Джон Кноеллер,

1
Чи слід завжди використовувати std::vectorзамість динамічно розподілених масивів? Які переваги використання масиву над вектором, і навпаки?
dreamlax

1
@John Knoller: ОП запитав про C ++ спосіб це зробити, я б сказав, що вектор - це спосіб C ++. Звичайно, ви вірні, що можуть бути ситуації, які все-таки вимагатимуть простого масиву, і не знаючи ситуації з ОП, це може бути одна. Я б не здогадувався, оскільки, мабуть, правдоподібно, що ОП не знає про вектори.
villintehaspam

1
@villintehaspam: Хоча це рішення не відповідає на моє запитання, це шлях, який я буду пройти. Тайлер Макенрі відповідає на моє запитання прямо, і він повинен допомогти особливо людям, які не можуть - з будь-якої причини - використовувати std::vector.
dreamlax

2
@villintehaspam: Ні, це не спосіб C ++. Це спосіб зробити Java. Наклеювання vectorскрізь незалежно від контексту називається "Написання Java-коду в C ++".
AnT

2

std::fillє одним із способів. Бере два ітератори та значення, яким потрібно заповнити регіон. Це, або цикл for, би (я гадаю) був би більш C ++ способом.

Встановлення масиву примітивних цілих типів на 0 конкретно, memsetце добре, хоча це може підняти брови. Розглянемо такожcalloc , хоча використовувати C ++ із-за ролі трохи незручно.

Зі свого боку, я майже завжди використовую петлю.

(Мені не подобається іншим здогадкам людей, але це правда, що std::vectorвсі речі рівні, бажано використовувати new[].)


1

Ви завжди можете використовувати мемсет:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));

Я розумію, що можу використовувати memset, але не був впевнений, чи це спосіб С ++ підходити до проблеми.
dreamlax

1
Це насправді не "шлях C ++", але тоді вони також не є сировинними масивами.
Піт Кіркхем

1
@gbrandt: Тобто слід сказати, що він не дуже добре працює ні в C, ні в C ++. Він працює для більшості значень типу char або unsigned char. Він працює для більшості типів значення 0 (принаймні, в більшості реалізацій). Інакше це взагалі марно.
Джері Коффін

1
10 * sizeof( *myArray )є більш задокументованим та стійким до змін, ніж 10 * sizeof( int ).
Кевін Рейд

1
У будь-якому випадку в ОП є необроблений масив, і мемсет - це найшвидший і найпростіший спосіб зняти нуль з цього масиву.
Грегор Брандт

-1

Зазвичай для динамічних списків елементів ви використовуєте a std::vector.

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

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