П'ять популярних міфів про C++, частина 2

Частина 1

4.2 Розділене володіння shared_ptr
Не у кожного об'єкта може бути один власник. Нам треба переконатися, що об'єкт знищений і звільнений, коли зникає остання посилання на нього. Таким чином, нам необхідна модель розділеного володіння об'єктом. Припустимо, у нас є синхронна чергу, sync_queue, для спілкування між завданнями. Відправник і одержувач отримують за вказівником на sync_queue:

void startup() 
{ 
sync_queue* p = new sync_queue{200}; // небезпека! 
thread t1 {task1,iqueue,p}; // task1 читає з *iqueue і пише в *p 
thread t2 {task2,p,oqueue}; // task2 читає з *p і пише в *oqueue 
t1.detach(); 
t2.detach(); 
} 


Передбачається, що task1, task2, iqueue і oqueue вже десь були відповідним чином визначені і прошу вибачення за те, що thread переживе область видимості, де вони були створені (за допомогою detatch()). Питання: хто видалить sync_queue, створені в startup()? Відповідь: той, хто останній буде використовувати sync_queue. Це класичний випадок, коли вимагається збирання сміття. Спочатку збірка підраховувала покажчики: потрібно зберігати кількість використань об'єкта, і в той момент, коли лічильник обнуляється, видаляти його. Безліч сучасних мов працюють так, а З++11 підтримує цю ідею через shared_ptr. Приклад перетворюється на:

void startup() 
{ 
auto p = make_shared<sync_queue>(200); // створити sync_queue і повернути покажчик на неї stared_ptr 
thread t1 {task1,iqueue,p}; // task1 читає з *iqueue і пише в *p 
thread t2 {task2,p,oqueue}; // task2 читає з *p і пише в *oqueue 
t1.detach(); 
t2.detach(); 
} 


Тепер деструктори task1 і task2 можуть знищити їх shared_ptr (і в більшості правильно побудованих систем так і зроблять), і останнє, що потрібно зробити — знищити sync_queue. Це просто і досить ефективно. Ніякої складної системи. Що важливо, вона не просто повертає пам'ять, пов'язану з sync_queue. Вона повертає об'єкт синхронізації (м'ютекс, блокування, що завгодно), вбудований в sync_queue, щоб синхронізувати дві нитки, виконують два завдання. Це не просто управління пам'яттю, це управління ресурсами. Цей «прихований» об'єкт синхронізації обробляється так само, як хендли файлів і потоків в попередньому прикладі. Можна спробувати позбутися від використання shared_ptr, ввівши унікального власника в якій-небудь області видимості, останньою в собі задачу, але це не завжди просто зробити — тому у С++11 є і unique_ptr (для одиночного володіння) and shared_ptr (для розділеного володіння).

4.3 Типобезопасность
Я говорив про складання сміття в контексті управління ресурсами. Але є ще і типобезопасность. У нас є операція delete, яку можна застосувати неправильно. Приклад:

X* p = new X; 
X* q = p; 
delete p; 
// ... 
q->do_something(); // пам'ять, відведена *p, могла бути перезаписана 


Не треба так робити. Безпосереднє застосування delete небезпечно і не потрібно в звичайних випадках. Залиште видалення класів, керуючим ресурсами — string, ostream, thread, unique_ptr і shared_ptr. Там видалення акуратно відстежуються.

4.4 Підсумок: ідеали управління ресурсами
З моєї точки зору, збірка сміття — останній засіб для управління ресурсами, а не рішення задачі і не ідеал.

1. Використовуйте відповідні абстракції, які рекурсивно і неявно обслуговують свої ресурси. Віддавайте перевагу їм, а не змінним в певній області видимості.
2. Коли вам необхідно використовувати вказівники та посилання, використовуйте розумні покажчики — unique_ptr і shared_ptr
3. Якщо нічого не допомагає (наприклад, ваш код — частина програми, яка заплуталася в покажчиках, і не використовує стратегію, підтримувану мовою, для управління ресурсами і обробки помилок), спробуйте обробляти ресурси, що не належать до пам'яті, вручну, і включайте збірку сміття для обробки неминучих витоків пам'яті.

5. Міф 4: для ефективності необхідно писати низькорівневий код

Багато хто вірить в те, що ефективний код зобов'язаний бути низькорівневим. Деякі навіть вірять, що низькорівневий код обов'язково ефективний. («Якщо воно таке потворне, напевно воно швидке! Хтось витратив купу часу і свого таланту для створення цієї штуковини!»). Звичайно, можна писати ефективний код на низькому рівні, і деякий код необхідно робити низькорівневим для роботи з машинними ресурсами. Вимірюйте, однак, варто це ваших зусиль. Сучасні компілятори С++ дуже ефективні, а архітектура сучасних машин надзвичайно складна. При необхідності такий низькорівневий код необхідно ховати за інтерфейсом для зручності. Часто приховування низькорівневого коду за високорівневим інтерфейсом сприяє оптимізації. Там, де важлива ефективність, спочатку спробуйте досягти її, висловивши ідею на високому рівні, не кидайтеся відразу на біти і покажчики.

5.1 qsort() в
Простий приклад. Якщо вам треба відсортувати набір чисел з плаваючою точкою за зменшення, ви могли б написати для цього код. Але якщо у вас немає екстремальних вимог (наприклад, чисел більше, ніж може вміститися в пам'яті), це було б наївно. За десятиліття ми зробили бібліотеки з алгоритмами сортування з прийнятною швидкістю роботи. Мені найменше подобається qsort() стандартної ISO бібліотеки C:

int greater(const void* p, const void* q) // тристороння порівняння
{ 
double x = *(double*)p; // отримати значення double з адреси p 
double y = *(double*)q; 
if (x>y) return 1; 
if (x<y) return -1; 
return 0; 
} 
void do_my_sort(double* p, unsigned int n) 
{ 
qsort(p,n,sizeof(*p),greater); 
} 
int main() 
{ 
double a[500000]; 
// ... fill a ... 
do_my_sort(a,sizeof(a)/sizeof(*a)); // передати вказівник і кількість елементів
// ... 
} 


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

Цей інтерфейс приховує інформацію. Ми сортуємо не байти — ми сортуємо double, але qsort цього не знає, тому нам треба надати інформацію про те, як порівнювати double, і скільки байтів у double. Звичайно, компілятор знає такі речі. Але низькорівневий інтерфейс qsort не дозволяє компілятору скористатися цією інформацією. Необхідність вказувати таку просту інформацію веде до помилок. Не переплутав я цілих два аргументу qsort? Якщо переплутаю, комп'ютер цього не помітить. Чи відповідає моя compare() угодами в C для тристороннього порівняння? Якщо ви подивитеся на промислову реалізацію qsort (рекомендую), ви побачите, скільки зусиль прикладено для компенсації нестачі інформації. Наприклад, досить важко провести зміну місцями елементів, заданих у вигляді кількості байт, щоб це було так само ефективно, як зміна місцями пари double. Витратні непрямі виклики функції порівняння можуть бути усунені компілятором тільки в тому випадку, якщо він застосує поширення констант для покажчиків на функції.

5.2 sort() в C++
Порівняємо qsort з його еквівалентом sort З с++

void do_my_sort(vector<double>& v) 
{ 
sort(v,[](double x, double y) { return x>y; }); // сортування v по зменшенню 
} 
int main() 
{ 
vector<double> vd; 
// ... fill vd ... 
do_my_sort(v); 
// ... 
} 


Тут потрібно менше пояснень. Вектору відомий його розмір, і нам не треба явно вказувати кількість елементів. Тип елементів не втрачається, і не потрібно пам'ятати про їх розмір. За замовчуванням, sort сортуються за зростанням, тому довелося задати критерій порівняння, як і для qsort. Тут він переданий в якості лямбда-вирази, що порівнює два double за допомогою >. І так вийшло, що ця лямбда тривіальним чином инлайнится всіма компіляторами С++, що я знаю, тому порівняння перетворюється в одну машинну операцію «більше, ніж» — ніяких неефективних викликів функції.

Я використовував контейнерну версію sort, щоб не ставити ітератори явно, тобто, щоб не писати:

std::sort(v.begin(),v.end(),[](double x, double y) { return x>y; }); 


Можна піти далі і використовувати об'єкт порівняння З++14:

sort(v,greater<>()); // сортування v по зменшенню


Яка з версій швидше? Можна скомпілювати версію qsort як З, так і З++ без всякий відмінностей у швидкодії, тому це буде швидше порівнянням стилів програмування, а не мов. Бібліотечні реалізації використовують один алгоритм для sort і qsort, тому це порівняння стилів програмування, а не алгоритмів. Звичайно, у різних бібліотек і компіляторів будуть різні результати, але для кожної реалізації буде видно розумне реакція на різні рівні абстракції.

Нещодавно Я прогнав приклади, і побачив, що sort в 2.5 рази швидше, ніж qsort. Це може змінюватися від компілятора до компілятор і від комп'ютера до комп'ютера, але жодного разу у мене qsort не виграв у sort. Іноді sort виконувався в 10 разів швидше. Чому? У стандартної бібліотеки С++ sort явно вище рівнем, ніж qsort, при цьому більш гнучкий і загальний. Він типобезопасен і параметризован на типі зберігання, тип елементів і критерієм сортування. Ніяких покажчиків, розмірів, байтів. Бібліотека STL, до якої належить sort, намагається не викидати ніякої інформації. Це призводить до чудовій инлайнингу і гарну оптимізацію.

Узагальнення і високорівневий код можуть вигравати у низькорівневого. Не завжди, але порівняння sort/qsort — це не поодинокий приклад. Завжди починайте з высокогоуровневой, точної і типобезопасной версії рішення. Оптимізуйте за необхідності.

6. Міф 5: С++ призначений для великих і складних програм

С++ — об'ємний мову. Розмір визначень схожий з З# і Java. Але це не означає, що вам потрібно знати кожну деталь, щоб використовувати його, чи використовувати всі функції безпосередньо в кожній програмі. Ось приклад використання основних компонент стандартної бібліотеки:

set<string> get_addresses(istream& is) 
{ 
set<string> addr; 
regex pat { R"((\w+([.-]\w+)*)@(\w+([.-]\w+)*))"}; // email address pattern 
smatch m; 
for (string s; getline(is,s); ) // read a line 
if (regex_search(s, m, pat)) // look for the pattern 
addr.insert(m[0]); // save address in set 
return addr; 
}


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

Для роботи приклад потрібно включити компоненти:

#include < string> 
#include<set> 
#include < iostream> 
#include<sstream> 
#include<regex> 
using namespace std; 


Перевіримо:

istringstream test { // ініціалізуємо потік рядком, що містить адреси
"asasasa\n" 
"[email protected]\n" 
"[email protected]$aaa\n" 
"[email protected] aaa\n" 
"asdf [email protected]\n" 
"[email protected]$$goo\n" 
"cft [email protected]@yy asas" 
"qwert\n" 
}; 
int main() 
{ 
auto addr = get_addresses(test); // get the email addresses 
for (auto& s : addr) // write out the addresses 
cout << s << '\n'; 
}


Просто приклад. Легко можна поміняти get_addresses(), щоб вона приймала регулярку як аргумент, щоб вона могла шукати URL або що завгодно. Легко поміняти get_addresses(), щоб вона розпізнавала більше одного входження шаблону в рядку. З++ призначений для гнучкості та узагальнень, але не кожна програма зобов'язана бути фреймворком. Суть в тому, що завдання вилучення емейлів з потоку просто виражається і просто перевіряється.

6.1 Бібліотеки
На будь-якій мові писати програму тільки через вбудовані можливості мови (if, for, і +) втомлює. І навпаки, при наявності відповідних бібліотек (graphics, route planning, database) будь-яку задачу можна виконати, доклавши максимум зусиль. Стандартна ISO бібліотека С++ відносно невелика (порівняно з комерційними), але крім неї є багато бібліотек як з вихідним кодом, так і комерційних. Приміром, за допомогою бібліотек Boost, POCO, AMP, TBB, Cinder, vxWidgets, CGAL складні речі стають простіше. Наприклад, нехай наша програмка витягує URL веб-сторінки. Для початку, ми узагальнимо get_addresses() для пошуку будь-якого рядка, що збігається з шаблоном.

set<string> get_strings(istream& is, regex pat) 
{ 
set<string> res; 
smatch m; 
for (string s; getline(is,s); ) // прочитати рядок 
if (regex_search(s, m, pat)) 
res.insert(m[0]); // зберегти збіг в наборі 
return res; 
}


Це спрощена версія. Тепер треба якось прочитати файл з інтернету. У Boost є бібліотека asio для роботи з вебом:

#include “boost/asio.hpp" // get boost.asio 


Спілкування з веб-сервером досить непроста:

int main() 
try { 
string server = "www.stroustrup.com"; 
boost::asio::ip::tcp::iostream s {server,"http"}; // встановити з'єднання
connect_to_file(s,server,"C++.html"); // перевірити і відкрити файл
regex pat {R"((http://)?www([./#\+-]\w*)+)"}; // URL 
for (x auto : get_strings(s,pat)) // шукаємо посилання
cout << x << '\n'; 
} 
catch (std::exception& e) { 
std::cout << "Exception: " << e.what() << "\n"; 
return 1; 
} 


При розборі файлу www.stroustrup.com's file C++.html це дає:

www-h.eng.cam.ac.uk/help/tpl/languages/C++.html
www.accu.org
www.artima.co/cppsource
www.boost.org


Я використовував набір, тому URL виводяться за алфавітом.
Я сховав перевірку з'єднання в connect_to_file():

void connect_to_file(iostream& s, const string& server, const string& file) 
// відкрити з'єднання з сервером і відкрити файл s 
// пропустити заголовки
{ 
if (!s) 
throw runtime_error{"немає з'єднання\n"}; 
// Запросити читання файлу з серверу 
s << "GET " << "http://"+server+"/"+file << " HTTP/1.0\r\n"; 
s << "Host: " << server << "\r\n"; 
s << "Accept: */*\r\n"; 
s << "Connection: close\r\n\r\n"; 
// Перевірити відповідь: 
string http_version; 
unsigned int status_code; 
s >> http_version >> status_code; 
string status_message; 
getline(s,status_message); 
if (!s || http_version.substr(0, 5) != "HTTP/") 
throw runtime_error{ "неправильний відповідь \n" }; 
if (status_code!=200) 
throw runtime_error{ "код статусу відповіді " }; 
// Викинути заголовки відповіді, які закінчуються порожнім рядком: 
string header; 
while (getline(s,header) && header!="\r"); 
} 


Я не писав все з нуля. Робота з HTTP скопійована з документації по asio.

6.2 Hello, World!
С++ — компільований мову, призначений для створення гарного, що обслуговується коду, для якого має значення швидкодія і надійність. Він призначався для змагань з інтерпретуються скриптовими мовами, які підходять для написання маленьких програм. JavaScript та інші подібні мови часто написані на С++. Тим не менш, є багато корисних програм на С++, які займають всього декілька десятків або сотень рядків.

Тут можуть допомогти автори бібліотек. Замість того, щоб концентруватися на незрозумілих і просунутих речі в бібліотеках, надайте прості приклади «hello, world!». Зробіть мінімальну версію бібліотеки, яку легко встановити, і приклад на одну сторінку з того, що вона вміє. В той або інший момент часу ми всі опиняємося в ролі новачка. До речі, ось моя версія «hello world» для С++:

#include < iostream> 
int main() 
{ 
std::cout << "Hello, World\n"; 
} 


Більш довгі і складні версії здаються мені менше прикольними.

7 Застосування міфів

Часто у міфів є підстава. Кожному з них відповідають моменти і ситуації, коли в них можна вірити на розумній основі, заснованому на доказах. На сьогоднішній день я вважаю їх абсолютно помилковими, простими непорозуміннями, хоча і отриманими чесним шляхом. Проблема в тому, що міфи завжди служать якоїсь мети, чи вони б вже вимерли. Ці п'ять міфів служать різним цілям:
— вони дають комфорт. Не потрібно нічого змінювати, переоцінювати і переосмислювати. Знайоме здається приємним. Зміни викликають тривогу, тому добре, якщо новинка буде нежиттєздатною.
— можна заощадити час. Якщо вам здається, що ви знаєте, що з себе представляє З++, вам не треба витрачати час на вивчення чого-небудь нового, експериментувати з новими технологіями, вимірювати код на швидкодію, тренувати новачків.
— можна не вчити С++. Якщо б ці міфи були правдою, навіщо його взагалі потрібно було б вчити?
— вони допомагають просувати інші мови і технології — у випадку їх правдивості це було б необхідно.

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

Прихильники «старого, доброго» програють. Витрати а підтримку часто більше, ніж на написання сучасного коду. Старі компілятори та інструменти дають менше швидкодії і найгірший аналіз, ніж сучасні. Хороші програмісти часто відмовляються від роботи з антикварним кодом.

Сучасні версії З++ і технології програмування, які він підтримує, відрізняються в кращий бік від того уявлення, яке створюють «загальновизнані міфи». Якщо ви вірите в якісь з них — не вірте мені на слово. Спробуйте, перевірте. Виміряйте «старий спосіб» і альтернативи для актуальної проблеми. Спробуйте освоїти нові методи, вивчити нові можливості та технології. Не забувайте порівнювати оціночну вартість підтримки нового і старого способів. Кращий спосіб спростування міфу — це представити доказ. Я представив вам свої приклади та аргументи.

І я не заявляю, що З++ ідеальний. Він не ідеальний, він не є найкращим мовою для всього і для всіх. Як і будь-який інший мову. Сприймайте його таким, який він є зараз, а не яким він був 20 років тому, і не таким, як його виставляє хтось, хто рекламує альтернативи. Щоб зробити раціональний вибір, пошукайте достовірну інформацію, і спробуйте самі зрозуміти, як сучасний С++ справляється з вашими завданнями.

8 Підсумок

Не вірте «загальновизнаним» знання С++, або бездоказательному його використання. У цій статті розглядаються п'ять популярних думок про С++ і пропонуються аргументи на користь того, що вони — всього лише міфи:

1. Щоб зрозуміти З++, спочатку потрібно вивчити З
2. С++ — це об'єктно-орієнтована мова програмування
3. В надійних програмах необхідна збірка сміття
4. Для досягнення ефективності необхідно писати низькорівневий код
5. З++ підходить тільки для великих і складних програм

Ці міфи шкідливі.

9 Зворотній зв'язок

Залишилися сумніви? Повідомте мені, чому. Які ще міфи ви зустрічали? Чому вони є міфами, а не правдою? Які у вас є докази їх викриття?

10 Посилання

1. ISO/IEC 14882:2011 Programming Language C++
2. POCO libraries: pocoproject.org/
3. Boost libraries: www.boost.org/
4. AMP: C++ Accelerated Massive Parallelism. msdn.microsoft.com/en-us/library/hh265137.aspx
5. TBB: Intel Threading Building Blocks. www.threadingbuildingblocks.org/
6. Cinder: A library for professional-quality creative coding. libcinder.org/
7. vxWidgets: A Cross-Platform GUI Library. www.wxwidgets.org
8. Cgal Обчислювальних Geometry Algorithms Library. www.cgal.org
9. Christopher Kohlhoff: Boost.Asio documentation. www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html
10. B. Stroustrup: Software Development for Infrastructure. Computer, vol. 45, no. 1, pp. 47-58, Jan. 2012, doi:10.1109/MC.2011.353.
11. Bjarne Stroustrup: The C++ Programming Language (4th Edition). Addison-Wesley. ISBN 978-0321563842. May 2013.
12. Bjarne Stroustrup: A Tour of C++. Addison Wesley. ISBN 978-0321958310. September 2013.
13. B. Stroustrup: Programming: Principles and Practice using C++ (2nd edition). Addison-Wesley. ISBN 978-0321992789. May 2014.

Післямова

Після публікації статті на isocpp.org отримали різні коментарі. Дозвольте прокоментувати деякі з них.

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

Це дослідницький матеріал, який докладно описує кожну деталь. Як я написав спочатку: «Кожному міфу можна присвятити книгу, але я обмежусь констатацією і коротким викладом своїх аргументів проти них».

Тим не менш, багато хто плутають приклади для ілюстрації точки зору з самою точкою зору. Деякі намагалися «спростувати спростування», змінюючи приклади, змінюючи обмеження прикладів, або оголошуючи приклади тривіальними. Приклади невеликі, т. к. вони повинні поміститися в невелику роботу. Але вони не так вже відрізняються від коду, який лежить в основі «справжніх» програм.

Деякі коментатори перейшли з С++11/З++14, на яких я засновував свою аргументацію, до більш старими версіями. З++14 — не С++ з 1980-х. Вони вже не те, чого вчилися більшість людей. І не те, чого навчаються на сьогоднішніх курсах. І не те, що люди бачать, розглядаючи досить об'ємні тексти існуючих програм. Я хочу змінити це уявлення. Якщо вам не вдається працювати з моїми прикладами в якій-небудь антикварними версії З++ або зі старим компілятором — це погано, але сьогодні є поліпшені версії всіх основних компіляторів (і зазвичай безкоштовні). В моїх прикладах не було ніякого ультрасучасного коду.

Кожна мова програмування, що досягає успіху, стикається з проблемами старого коду. Не судіть С++ по технологіям програмування 20-річної давності або компиляторам 10-річної давності. Погляньте на сучасний С++ і спробуйте скористатися новими можливостями, як це вже вдалося багатьом. Сьогодні ви майже напевно користувалися програми, написаної на С++11. Між моїм і вашим комп'ютером дуже багато кроків, на яких зустрічаються програми на С++11.

Досить багато коментарів містять заяви на кшталт «а в мові Х є точно така ж можливість» або «бібліотека Y в мові X робить саме це». Очевидно, якщо у вас є мова, в якому простіше, ніж С++, вирішити ваше завдання, при цьому не втрачаючи критично швидкодії, переносимості і не купуючи непотрібних обмежень — використовуйте його. Але жодна мова або бібліотека не ідеально підходять для всіх і всього.

Я надав приклади для загальних завдання і загальних технологій. Порівнювати щось з одним прикладом не особливо потрібно. Моя точка зору належить до загальних речей, а приклади — просто ілюстрації. При використанні досить хорошої бібліотеки будь-яка мова буде простим і приємним. Для досить обмеженої задачі можна сконструювати спеціальний мову, який буде елегантніше мови загального призначення. Приміром, бібліотека asio, яку я використав у пункті 6.1 — гнучка, ефективна мережева бібліотека загального призначення. Для будь-якої задачі її можна обернути в просту функцію (або невеликий набір функцій), щоб зробити її більш зручною. І мій код був би реалізацією цього. Те, що я намагався пояснити в п. 6.2 — спільнота програмістів З++ могли б допомогти програмістам, провівши більше часу над тим, щоб робити прості речі простішими. Наприклад, в 99% випадків я використовую sort(v) замість sort(v.begin(),v.end()).

Швидкодія

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

Я маю на увазі сучасну реалізацію С++, відповідну стандартам. Наприклад, коли я пишу про швидкодію оптимізації коротких рядків, я не маю на увазі реалізації С++ до С++11. Я не розглядаю коментарі на тему, що std::sort() або std::string працюють повільно без використання симулятора. Зрозуміло — але безглуздо обговорювати швидкодію неоптимизированного коду. При використанні GCC або Clang використовуйте-O2; для продуктів від Microsoft використовуйте release mode.

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

Так, C-версія compose() не перевіряє значення, що повертається malloc(). Я ж питав у вас, чи все я правильно зробив. Я навмисно не надав вам код, придатний для продакшену. Відсутність перевірки результату — один з основних джерел помилок, тому моя помилка була спеціально зроблена для ілюстрації цього. В даному випадку часто допомагають виключення. Звичайно, можна було написати З-версію compose(), використовуючи менш відомі функції стандартної бібліотеки, і так, можна було уникнути вільного зберігання, якщо дозволити викликає кодом передати розміщений в стеку буфер і дозволити викликає розбиратися з проблемою строкових аргументів, які б його переповнили. Тим не менше, ці альтернативи не відносяться до головного питання: такий код писати складніше, ніж в З++, і ще складніше писати його правильно. Новачки з першого разу пишуть версію для С++, але не для З, особливо для тих версій, які засновані на функції стандартної бібліотеки, яким не навчають новачків.

З++ використовувався в критичних і високонавантажених вбудованих системах роками — в тих же марсіанських Роверах (аналіз картинки і автономна робота), F-35, F-16 (системи управління польотом), і безлічі інших: www.stroustrup.com/applications.html. І так, космічна капсула Оріон запрограмована за допомогою С++.

Бібліотеки

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

На жаль, часто бібліотеки С++ не розробляються з урахуванням спільної роботи з іншими. І ні одного місця, де можна було б брати всі бібліотеки. Я роками спостерігав за процесом навчання студентів за схемою «спочатку», і читав ці програми десятиліттями. Тисячам людей я викладав С++ в якості першої мови. Мої заяви про можливості навчання З++ засновані на великому досвіді.

З++ навчати легше, ніж З більш гарною системи типів і синтаксису. Необхідно вчити менше трюків і милиць. Уявіть, як би ви стали вчити стилю програмування на С, навчаючи мови С++. Я б ніколи не став давати новачкам курс З++, який би:

— не містив гарної основи щодо роботи з пам'яттю, покажчиками, і т. д.
— не давав студентам уявлення про «чистому» та про його використання
— не обгрунтовував більшість можливостей мови
— намагався навчити абсолютно всім технікам С++

Хороші вчителі, які викладають З, не намагаються навчити новачків всім технікам.
www.stroustrup.com/programming.html — моя відповідь на запитання «Як би ви навчали новачків С++?». Ця система працює.

Можна ознайомитися з моєї досить старою роботою по деяким аспектам викладання З і С++: Learning Standard C++ as a New Language. C/C++ Users Journal. pp 43-54. May 1999 (www.stroustrup.com/papers.html).

Сьогодні я б зробив З-версію курсу краще, а З++ — сильно краще. Приклади відображають стиль програмування того часу (і були розглянуті експертами з програмування на С і С++).

Сьогоднішній С++ — це стандарт ISO С++14, а не те, що я описував 30 років тому, і не те, що ваш викладач розповідав вам 20 років тому. Вивчіть C++11/C++14 у тому вигляді, в якому вони підтримуються основними компіляторами, і звикнете до них. Це набагато кращий інструмент, ніж ранні версії З++. Сьогоднішній С — це стандарт ISO С11, а не " K&R C (хоча я не впевнений, чи відповідають сьогоднішні компілятори стандарту С11 так само добре, як компілятори С++ стандарту З++14). Мене шокують деякі речі, які сьогодні викладають під виглядом «правильного С++».

С++ — це не ООП-мову. Це мова, що підтримує ООП, інші техніки програмування, і їх комбінації. Якщо ви — досвідчений програміст, я рекомендую прочитати A Tour of C++ в якості швидкого огляду сучасного мови C++.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.