Чи краще в C ++ проходити за значенням або проходити за постійною посиланням?
Мені цікаво, яка краща практика. Я розумію, що постійне посилання має забезпечувати кращі показники роботи програми, оскільки ви не створюєте копію змінної.
Чи краще в C ++ проходити за значенням або проходити за постійною посиланням?
Мені цікаво, яка краща практика. Я розумію, що постійне посилання має забезпечувати кращі показники роботи програми, оскільки ви не створюєте копію змінної.
Відповіді:
Раніше зазвичай рекомендуються найкраща практика 1 для використання проходу по константностей іому для всіх типів , для вбудованих типів (крім char
, int
, double
і т.д.), для ітераторів і функціональних об'єкти (лямбда, класів , що випливають з std::*_function
).
Особливо це стосувалося до існування рухової семантики . Причина проста: якщо ви перейшли за значенням, довелося зробити копію об'єкта і, за винятком дуже маленьких об'єктів, це завжди дорожче, ніж проходження посилання.
За допомогою C ++ 11 ми отримали семантику руху . У двох словах, семантика переміщення дозволяє в деяких випадках об'єкт можна передавати «за значенням», не копіюючи його. Зокрема, це той випадок, коли об'єкт, який ви передаєте, є релевантним .
Само по собі переміщення предмета все ще принаймні так само дорого, як і проходження посиланням. Однак у багатьох випадках функція все-таки внутрішньо копіює об’єкт - тобто вона буде мати право власності на аргумент. 2
У цих ситуаціях ми маємо наступні (спрощені) компроміси:
"Передати значення" все ще призводить до того, що об'єкт буде скопійовано, якщо тільки об'єкт не є оцінкою. У випадку ревальвації об'єкт може бути переміщений замість цього, так що другий випадок раптом більше не "скопіювати, потім перемістити", а "перемістити, потім (потенційно) перемістити знову".
Для великих об'єктів, які реалізують належні конструктори переміщення (такі як вектори, рядки ...), другий випадок тоді набагато ефективніший, ніж перший. Тому рекомендується використовувати значення передачі за значенням, якщо функція бере власність на аргумент, і якщо тип об'єкта підтримує ефективне переміщення .
Історична записка:
Насправді, будь-який сучасний компілятор повинен мати можливість з'ясувати, коли передача за значенням дорога, і неявно перетворити виклик, щоб використовувати const ref, якщо це можливо.
Теоретично. На практиці компілятори не завжди можуть змінити це, не порушивши бінарний інтерфейс функції. У деяких особливих випадках (коли функція вбудована) копія буде фактично залишена, якщо компілятор зможе зрозуміти, що оригінальний об'єкт не буде змінений через дії функції.
Але в цілому компілятор не може цього визначити, і поява семантики переміщення в C ++ зробила цю оптимізацію набагато менш актуальною.
1 Напр. У Скотта Майєрса, Ефективний C ++ .
2 Особливо часто це стосується конструкторів об'єктів, які можуть брати аргументи та зберігати їх всередині, щоб бути частиною стану побудованого об'єкта.
Редагувати: Нова стаття Дейва Абрахамса на cpp-next:
Передача за значенням для структур, де копіювання дешеве, має додаткову перевагу в тому, що компілятор може вважати, що об'єкти не псевдоніми (не є однаковими об'єктами). Використовуючи пропускний посилання, компілятор не може вважати це завжди. Простий приклад:
foo * f;
void bar(foo g) {
g.i = 10;
f->i = 2;
g.i += 5;
}
компілятор може оптимізувати його в
g.i = 15;
f->i = 2;
оскільки він знає, що f і g не мають однакового місця. якби g був посиланням (foo &), компілятор не міг би цього припустити. оскільки gi тоді може бути псевдонімом f-> i і має мати значення 7. тож компілятору доведеться повторно отримати нове значення gi з пам'яті.
Для більш дотичних правил, ось хороший набір правил, знайдений у статті Move Constructors (настійно рекомендується прочитати).
Вище "примітивні" означають, в основному, невеликі типи даних, які мають довжину в декілька байтів і не є поліморфними (ітератори, об'єкти функцій тощо) або копіюють дорого. У цьому документі є ще одне правило. Ідея полягає в тому, що іноді хочеться зробити копію (у випадку, якщо аргумент неможливо змінити), а іноді не хоче (у випадку, якщо хтось хоче використовувати сам аргумент у функції, якщо аргумент все одно був тимчасовим , наприклад). У статті детально пояснено, як це можна зробити. У мові C ++ 1x ця техніка може бути використана в основному за допомогою мовної підтримки. До цього часу я б пішов з вищезазначеними правилами.
Приклади: щоб зробити верхній регістр рядка та повернути верхні регістри, завжди слід проходити за значенням: все одно потрібно взяти його копію (не можна змінити посилання const безпосередньо) - тому краще зробіть її максимально прозорою для абонент і зробить цю копію рано, щоб абонент міг максимально оптимізувати - як детальніше в цьому документі:
my::string uppercase(my::string s) { /* change s and return it */ }
Однак якщо параметр все одно не потрібно змінювати, візьміть його за посиланням на const:
bool all_uppercase(my::string const& s) {
/* check to see whether any character is uppercase */
}
Однак, якщо ви маєте на меті цього параметра написати щось в аргумент, то передайте це за допомогою посилань, які не мають const
bool try_parse(T text, my::string &out) {
/* try to parse, write result into out */
}
__restrict__
(які також можуть працювати над посиланнями), ніж робити зайві копії. Занадто поганий стандарт C ++ не прийняв restrict
ключове слово C99 .
Залежить від типу. Ви додаєте невеликі накладні витрати на необхідність зробити довідку та переналагодження. Для типів розміром, рівним або меншим, ніж покажчики, які використовують за замовчуванням копіюючий копій, можливо, швидше пройти за значенням.
Як було зазначено, це залежить від типу. Для вбудованих типів даних найкраще передавати значення. Навіть деякі дуже маленькі структури, такі як пара вкладених елементів, можуть працювати краще, передаючи значення.
Ось приклад, припустимо, що у вас є ціле значення, і ви хочете передати його в іншу процедуру. Якщо це значення було оптимізоване для зберігання в реєстрі, то, якщо ви хочете передати це посилання, воно спочатку має бути збережене в пам'яті, а потім вказівник на цю пам'ять, розміщений у стеку, щоб здійснити виклик. Якщо воно передається за значенням, все, що потрібно, - це регістр, висунутий на стек. (Деталі трохи складніші, ніж ті, що даються для різних систем виклику та процесорів).
Якщо ви займаєтеся програмуванням шаблонів, зазвичай ви змушені завжди проходити через const ref, оскільки ви не знаєте типів, які передаються. Штрафи за передачу чогось поганого за значенням набагато гірші, ніж штрафи за передачу вбудованого типу за const ref.
Це те, над чим я зазвичай працюю, розробляючи інтерфейс нешаблонної функції:
Передайте значення, якщо функція не хоче змінювати параметр, а значення недорого копіювати (int, double, float, char, bool тощо). Зауважте, що std :: string, std :: vector та інше контейнерів у стандартній бібліотеці НЕ)
Пройдіть по const pointer, якщо значення дорого копіюється, а функція не хоче змінювати вказане значення, а NULL - це значення, яке функціонує функція.
Пройдіть повз покажчик non-const, якщо значення дорого копіюється, а функція хоче змінити вказане значення, а NULL - це значення, яке функціонує функція.
Передайте посилання const, коли значення дорого копіюється, і функція не хоче змінювати згадане значення, і NULL не було б дійсним значенням, якби замість цього використовувався покажчик.
Пройдіть по посиланням без переліку, коли значення копіюється дорого, і функція хоче змінити вказане значення, і NULL не було б дійсним значенням, якби замість цього використовувався покажчик.
std::optional
до зображення, і вам більше не потрібні покажчики.
Здається, ти отримав свою відповідь. Передача за вартістю дорога, але дає вам копію, з якою можна працювати, якщо вона вам потрібна.
Як правило, краще проходити посилання const. Але якщо вам потрібно змінити функціональний аргумент на локальному рівні, вам слід скористатися передачею за значенням. Для деяких основних типів продуктивність в цілому однакова як для проходження за значенням, так і за посиланням. Насправді посилання, представлене внутрішньо покажчиком, саме тому, можна очікувати, що, наприклад, і для обох покажчиків однакові за показниками продуктивності, або навіть передача за значенням може бути швидшою через непотрібну відсічку.
Як правило, величина для некласових типів та посилання const для класів. Якщо клас дійсно малий, то, ймовірно, краще передавати значення, але різниця мінімальна. Чого ви насправді хочете уникнути, це передавати якийсь гігантський клас за значенням і все це дублювати - це призведе до величезної різниці, якщо ви передасте, скажімо, std :: vector з досить великою кількістю елементів у ньому.
std::vector
насправді розподіляє свої елементи на купу, а сам векторний об'єкт ніколи не росте. Чекай. Якщо операція призведе до того, що буде зроблена копія вектора, вона фактично піде і дублює всі елементи. Це було б погано.
sizeof(std::vector<int>)
є постійним, але передавання його за значенням все одно буде копіювати вміст за відсутності будь-якої чіткості компілятора.
Передайте значення за малі типи.
Передайте посилання const для великих типів (визначення великого може змінюватись в залежності від машин), але в C ++ 11 передайте значення, якщо ви збираєтеся споживати дані, оскільки ви можете використовувати семантику переміщення. Наприклад:
class Person {
public:
Person(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};
Тепер код виклику зробив би:
Person p(std::string("Albert"));
І тільки один об’єкт буде створений і переміщений безпосередньо в член name_
в класі Person
. Якщо ви проходите через посилання const, для його введення потрібно буде зробити копію name_
.
Проста різниця: - У функції у нас є параметр вводу та виводу, тому якщо параметр введення та виходу є однаковим, тоді використовуйте виклик за посиланням інше, якщо параметр вводу та виходу відрізняються, тоді краще використовувати виклик за значенням.
приклад void amount(int account , int deposit , int total )
вхідний параметр: рахунок, параметр виходу депозиту: загальний
вхід і вихід - це різні дзвінки з використанням vaule
void amount(int total , int deposit )
вхідний загальний обсяг виходу депозиту