Контейнери STL істотно знижують складність написання коду, працюючого з динамічними структурами даних і, найголовніше, підвищують його безпомилковість. Але в деяких випадках, при роботі з великими елементами даних в контейнерах, ця техніка може породжувати неминучу втрату продуктивності. Это связано с тем, що починаючи з операції приміщення елемента в контейнер, і всі наступні переміщення елементів можуть вимагати копіювання елементів.
Примітка: Ступінь вираженості таких ефектів залежить і від типу контейнера, і від розглянутих операцій. Наприклад, операції взаємного обміну 2-х елементів в ході сортування потребуватиме 3-х копіювань для контейнера типу vector і не потребуватиме додаткових копіювань для контейнера list. Але операції початкового приміщення елемента в контейнер (push_back(), вставити() та ін.) завжди виконуються копіюванням.
Це може стати проблемою для додатка, коли операції на контейнерах критичні за часом виконання (або здаються вам такими). Є і ще більш складні випадки, коли для класу об'єктів контейнера не визначена операція копіювання, або коли об'єкти представляють собою самі посилальні об'єкти, повне копіювання для яких необхідно виконувати рекурсивними процедурами проходження по всіх посиланнях (те, що в мові Python і інших називають глибоким копіюванням).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | #include <iostream> #include <vector> #include <algorithm> #include <string> using namespace std; struct data { // запись о студенте string fio; int group, age, scholarship; data(string fio, int group, int age, int scholarship = 0) : fio(fio), group(group), age(age), scholarship(scholarship) { cout << '+'; } data(const data& d) : fio(d.fio), group(d.group), age(d.age), scholarship(d.scholarship) { cout << '+'; } ~data(void) { cout << '-'; } inline friend ostream& operator <<(ostream& out, const data& obj) { return out << "[ " << obj.fio << " : " << obj.group << " : " << obj.age << " : " << obj.scholarship << " ]"; } }; struct comp_data { // функтор сравнения int what; bool compare(const struct data& f, const struct data& s) { switch (abs(what)) { case 1: return f.fio < s.fio; case 2: return f.group < s.group; case 3: return f.age < s.age; case 4: return f.scholarship < s.scholarship; default: return false; } } public: comp_data(int what) : what(what) {} bool operator()(const struct data *f, const struct data *s) { bool ret = compare(*f, *s); return what >= 0 ? ret : !ret; } }; int main(void) { setlocale(LC_ALL, "rus"); struct data list[] = { { "Сидоров С.С.", 12, 19, 1500 }, { "Иванов И.И.", 13, 20 }, { "Петров П.П.", 11, 21 }, { "Чапаев В.И.", 10, 45, 2000 }, }; cout << endl; vector< struct data* > filology; for (unsigned i = 0; i < sizeof(list) / sizeof(list[0]); i++) { filology.push_back(new struct data(list[i])); } cout << endl; while (true) { for (auto x : filology) cout << *x << endl; cout << "поле сортировки? : "; int mode; cin >> mode; if (!cin || (cin.rdstate() & ios::eofbit)) { cout << endl; break; } sort(filology.begin(), filology.end(), comp_data(mode)); } auto i = filology.begin(); while (i != filology.end()) { // filology.clear(); filology.erase(i); } cout << "размер журнала = " << filology.size() << endl; } |
Все працює благополучно, як і раніше - сортуємо набір записів по довільним полях і в будь-якому порядку:
(Зверніть увагу, що завершувати це додаток потрібно по Ctrl + D - End Of File ... в Widows, мабуть, по Ctrl + Z.)
Але! ... Спеціально були залишені налагоджувальні «сліди» спрацьовувань конструкторів і деструкторів записів, і записи конструюються 8 раз, а деструкція спрацьовує тільки 4, для локального масиву в точці виходу з блоку. Локальний масив для нас взагалі не представляє інтересу. Він введений для спрощення прикладу тільки як набір ініціюючих значень.
А ось для записів, розміщені в контейнер, знищення записів не відбувається, і ми отримуємо відверту витік пам'яті. Але гірше того, після того як ми видаляємо елемент з контейнера (не роблячи додаткових дій), ми і не зможемо видалити запис, викликавши для неї delete. Это потому, що після виклику прати() ми втратили до запису єдиний шлях доступу через итератор (в коді показаний цикл з прати(), так наочніше, что эквивалентно ясно(), ефект якого, буде тим же самим).
висновок, який може бути зроблений з прикладу, виглядає так:
поміщаючи в контейнери не об'єкти, а покажчики на них, можна помітно знизити обчислювальні витрати на маніпуляції з ними (але чи завжди є принциповим цей виграш при нинішніх обчислювальних потужностях?).
заміна об'єктів на покажчики в контейнерах робить код у багато разів небезпечнішим в сенсі прихованих тонких помилок, причому такого серйозного рівня, які можуть далі приводити до краху додатки.
Тут деяку допомогу можуть надати розумні покажчики з останніх стандартів C ++ (shared_ptr или weak_ptr, але не unique_ptr і не старий добрий і проблемний auto_ptr), нам для цього в попередньому коді досить змінити 4 рядки:
1 2 3 4 5 6 7 | #include <memory> ... bool operator()(const shared_ptr< struct data > f, const shared_ptr< struct data > s){ ... vector< shared_ptr< struct data > > filology; ... filology.push_back(shared_ptr< struct data >(new struct data(list[i]))); |
У Windows для shared_ptr необхідний #include <пам'ять> , а в інших системах не обов'язково.
И поведінка додатки істотно зміниться:
Але не слід безоглядно спокушатися, так як і розумні покажчики, знімаючи одні, породжують інші потенційні турботи (такі як циклічні посилання і ін. про які досить багато написано).
Бюджет на рекламу і не лише
Бюджет на рекламу і не лише у понеділок зранку
Чекатиму від вас було отримано лист із запитом на підтвердження прочитання книги та журнали та книги та статті експертів
Бююююююаьпеов од Білий дім 77атеофпрф підходах ложка пізнаючи схожі пізнати означати архея синій птах 6