TL; DR: Проходити через посилання const все ще є хорошою ідеєю в C ++, все враховано. Не передчасна оптимізація.
TL; DR2: Більшість пристосувань не мають сенсу до тих пір, поки це не відбудеться.
Мета
Ця відповідь просто намагається трохи розширити пов'язаний елемент у Основних керівних принципах C ++ (вперше згаданий у коментарі Амона).
Ця відповідь не намагається вирішити питання про те, як мислити та правильно застосовувати різні пристосування, широко розповсюджені в колах програмістів, особливо питання примирення між суперечливими висновками чи доказами.
Застосовуваність
Ця відповідь стосується лише функціональних викликів (нерозбірних вкладених областей на одній нитці).
(Бічна примітка.) Коли прохідні речі можуть вийти за межі сфери (тобто мати тривалість життя, яка потенційно перевищує зовнішню область), стає важливішим задовольнити потребу програми в управлінні життєдіяльністю об'єкта раніше, ніж будь-що інше. Зазвичай для цього потрібне використання посилань, які також можуть довічно керувати, наприклад, смарт-покажчики. Альтернативою може бути використання менеджера. Зауважимо, що лямбда - це свого роду знімна область; лямбда-знімки поводяться так, ніби мають об’єктну область. Тому будьте обережні з захопленням лямбда. Також будьте уважні до того, як передається сама лямбда - копія або посилання.
Коли пройти за значенням
Для значень, які є скалярними (стандартні примітиви, які вписуються в машинний реєстр і мають значення смислових значень), для яких немає потреби в комунікації за допомогою мутаційності (спільна посилання), передайте за значенням.
У ситуаціях, коли виклику потрібне клонування об'єкта чи сукупності, передайте значення, в якому копія виклику відповідає потребі клонованого об'єкта.
Коли проходити посилання та ін.
для всіх інших ситуацій, проходьте повз покажчики, посилання, розумні покажчики, ручки (див. ідіома рукоятки тіла) тощо. Кожного разу, коли дотримуватися цієї поради, застосовуйте принцип коректності коректності, як завжди.
Речі (агрегати, об'єкти, масиви, структури даних), які мають достатньо великий розмір пам’яті пам'яті, завжди повинні бути розроблені для полегшення проходження посилання з міркувань продуктивності. Ця порада безумовно застосовується, коли вона становить сотні байтів і більше. Ця порада є прикордонною, коли вона складає десятки байтів.
Незвичайні парадигми
Існують спеціальні парадигми програмування, які за задумом важкі для копіювання. Наприклад, обробка рядків, серіалізація, мережева комунікація, ізоляція, обгортання сторонніх бібліотек, міжпроцесорна комунікація спільної пам'яті тощо. У цих областях додатків або парадигмах програмування дані копіюються з структур у структури або іноді перепаковуються в байтові масиви.
Як мовна специфікація впливає на цю відповідь, перш ніж розглядати оптимізацію.
Sub-TL; DR Поширення посилання не повинно викликати код; проходження через const-посилання задовольняє цьому критерію. Однак всі інші мови без особливих зусиль задовольняють цей критерій.
(Новачкам програмістам на C ++ рекомендується повністю пропустити цей розділ.)
(Початок цього розділу частково натхненний відповіддю gnasher729. Однак дійшов інший висновок.)
C ++ дозволяє визначені користувачем конструктори копій та оператори призначення.
(Це (був) сміливий вибір, який (був) і дивовижним, і шкодуючим. Це, безумовно, розбіжність від прийнятної сьогодні норми в мовному дизайні.)
Навіть якщо програміст C ++ не визначає жодного, компілятор C ++ повинен генерувати такі методи на основі мовних принципів, а потім визначати, чи потрібно виконувати додатковий код, крім memcpy
. Наприклад, a class
/, struct
що містить astd::vector
члена, повинен мати конструктор копій та оператор присвоєння, який нетривіальний.
В інших мовах конструктори копій та клонування об’єктів не перешкоджають (за винятком випадків, коли це абсолютно необхідно та / або мають значення для семантики програми), оскільки об'єкти мають референтну семантику за мовним дизайном. Ці мови, як правило, мають механізм збору сміття, який базується на доступності замість власності на основі сфери чи підрахунку довідок.
Коли в C ++ (або C) передається посилання або покажчик (включаючи посилання const), програміст впевнений, що ніякий спеціальний код (визначений користувачем або створений компілятором функцій) не буде виконаний, крім поширення адресного значення (посилання або вказівник). Це чіткість поведінки, з якою програмісти C ++ почувають себе комфортно.
Однак задля того, що мова С ++ є надмірно складною, так що ця чіткість поведінки нагадує оазис (живуче середовище існування) десь навколо зони ядерних випадінь.
Щоб додати більше благ (або образи), C ++ вводить універсальні посилання (r-значення), щоб полегшити визначені користувачем оператори переміщення (конструктори переміщення та оператори присвоєння переміщення) з хорошою продуктивністю. Це корисно для надзвичайно актуального випадку використання (переміщення (перенесення) об'єктів з одного примірника в інший), зменшуючи потребу в копіюванні та глибокому клонуванні. Однак в інших мовах нелогічно говорити про таке переміщення предметів.
(Розділ поза темою) Розділ, присвячений статті "Хочеш швидкості? Перейди цінністю!" написано близько 2009 року.
Ця стаття була написана в 2009 році і пояснює виправдання дизайну для r-значення в C ++. Ця стаття представляє вагомий контргумент моєму висновку в попередньому розділі. Однак приклад коду статті та претензія на ефективність давно спростовані.
Sub-TL; DR Дизайн семантики значень r в C ++ дозволяє отримати напрочуд елегантну семантику на стороні користувача наSort
функції користувача. Цього елегантного неможливо моделювати (імітувати) іншими мовами.
Функція сортування застосовується до цілої структури даних. Як було сказано вище, це було б повільно, якщо береться багато копіювання. Як оптимізація продуктивності (що практично актуально), функція сортування розрахована на руйнівну у багатьох кількох мовах, окрім C ++. Руйнівний означає, що цільова структура даних модифікується для досягнення мети сортування.
У програмі C ++ користувач може обрати одну з двох реалізацій: руйнівну з кращою продуктивністю або звичайну, яка не змінює вхід. (Шаблон опущено для стислості.)
/*caller specifically passes in input argument destructively*/
std::vector<T> my_sort(std::vector<T>&& input)
{
std::vector<T> result(std::move(input)); /* destructive move */
std::sort(result.begin(), result.end()); /* in-place sorting */
return result; /* return-value optimization (RVO) */
}
/*caller specifically passes in read-only argument*/
std::vector<T> my_sort(const std::vector<T>& input)
{
/* reuse destructive implementation by letting it work on a clone. */
/* Several things involved; e.g. expiring temporaries as r-value */
/* return-value optimization, etc. */
return my_sort(std::vector<T>(input));
}
/*caller can select which to call, by selecting r-value*/
std::vector<T> v1 = {...};
std::vector<T> v2 = my_sort(v1); /*non-destructive*/
std::vector<T> v3 = my_sort(std::move(v1)); /*v1 is gutted*/
Окрім сортування, ця елегантність також корисна при здійсненні деструктивного алгоритму медіанного пошуку в масиві (спочатку несортованому) шляхом рекурсивного розподілу.
Однак зауважте, що більшість мов застосовуватимуть збалансований двійковий пошук дерева пошуку до сортування, замість того, щоб застосовувати деструктивний алгоритм сортування до масивів. Тому практична актуальність цієї методики не така висока, як здається.
Як оптимізація компілятора впливає на цю відповідь
Коли вбудоване (а також оптимізація цілої програми / оптимізація посилання-часу) застосовується на декількох рівнях викликів функцій, компілятор може бачити (іноді вичерпно) потік даних. Коли це станеться, компілятор може застосувати безліч оптимізацій, деякі з яких можуть усунути створення цілих об'єктів у пам'яті. Як правило, коли ця ситуація застосовується, не має значення, чи параметри передаються за значенням або const-посиланням, оскільки компілятор може проаналізувати вичерпно.
Однак якщо функція нижчого рівня викликає щось, що не підлягає аналізу (наприклад, щось у іншій бібліотеці поза компіляцією, або графік виклику, який є занадто складним), тоді компілятор повинен оптимізувати захист.
Об'єкти, що перевищують значення реєстру машини, можуть бути скопійовані явними інструкціями щодо завантаження / зберігання пам'яті або викликом поважної memcpy
функції. На деяких платформах компілятор генерує інструкції SIMD для переміщення між двома місцями пам'яті, кожна інструкція переміщує десятки байтів (16 або 32).
Дискусія з питань багатослівності чи зорового скупчення
Програмісти на C ++ звикли до цього, тобто, поки програміст не ненавидить C ++, накладні витрати на написання чи читання const-посилань у вихідному коді не жахливі.
Аналіз витрат і вигод, можливо, робився багато разів раніше. Я не знаю, чи є якісь наукові, які слід цитувати. Думаю, більшість аналізів були б ненауковими чи невідтворюваними.
Ось що я уявляю (без доказів чи достовірних посилань) ...
- Так, це впливає на продуктивність програмного забезпечення, написаного цією мовою.
- Якщо компілятори можуть зрозуміти призначення коду, потенційно це може бути досить розумним для автоматизації цього
- На жаль, у мовах, що сприяють мутаційності (на відміну від функціональної чистоти), компілятор класифікував би більшість речей як мутовані, тому автоматичне виведення constness відхиляло б більшість речей як non-const
- Розумовий наклад залежить від людей; люди, які вважають, що це душевний розум, можуть відкинути C ++ як життєздатну мову програмування.