Чи є різниця в ефективності між i ++ та ++ i в C ++?


352

У нас виникає питання , чи є різниця в продуктивності між i++і ++i в C ?

Яка відповідь на C ++?


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

104
Чи є різниця в продуктивності між використанням C ++ та ++ C?
new123456

2
Стаття: Чи розумно використовувати оператор збільшення префікса ++ це замість оператора postfix це ++ для ітераторів? - viva64.com/uk/b/0093

Відповіді:


426

[Резюме: Використовуйте, ++iякщо у вас немає конкретної причини для використання i++.]

Для C ++ відповідь дещо складніша.

Якщо iце простий тип (не екземпляр класу C ++), то відповідь, задана для C ("Ні, немає різниці в продуктивності"), має місце, оскільки компілятор генерує код.

Однак якщо iце екземпляр класу C ++, тоді i++і ++iздійснюють виклики до однієї з operator++функцій. Ось стандартна пара цих функцій:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

Оскільки компілятор не генерує код, а просто викликає operator++функцію, немає можливості оптимізувати tmpзмінну та пов'язаний з нею конструктор копій. Якщо конструктор копій дорогий, то це може мати значний вплив на продуктивність.


3
Що компілятор може уникнути, це друга копія, яка повертає tmp, виділяючи tmp у абонента через NRVO, як згадується в іншому коментарі.
Blaisorblade

7
Чи не може компілятор уникнути цього взагалі, якщо вводиться оператор ++?
Едуард - Габріель Мунтяну

16
Так, якщо оператор ++ вбудований, а tmp ніколи не використовується, його можна видалити, якщо конструктор або деструктор об'єкта tmp не має побічних ефектів.
Зан Лінкс

5
@kriss: різниця між C і C ++ полягає в тому, що в C ви маєте гарантію, що оператор буде накреслений, і в цей момент гідний оптимізатор зможе усунути різницю; натомість у C ++ ви не можете припустити, що вбудовується - не завжди.
Blaisorblade

3
Я б поставив +1 Якщо б у відповіді було сказано щось про класи, які містять покажчики (автоматичні, розумні чи примітивні) на динамічно виділену (купу) пам'ять, де конструктор копій обов'язково виконує глибокі копії. У таких випадках аргументу немає, ++ i, можливо, на порядок ефективніший, ніж i ++. Їх ключове значення полягає в тому, щоб використовувати звичку використовувати попередній приріст, коли семантика після збільшення фактично не вимагається вашим алгоритмом, і тоді ви будете мати звичку писати код, який за своєю природою піддається більшої ефективності, незалежно від того, як добре ваш компілятор може оптимізувати.
телефонінг

64

Так. Існує.

Оператор ++ може або не може бути визначений як функція. Для примітивних типів (int, double, ...) вбудовані оператори, тому компілятор, ймовірно, зможе оптимізувати ваш код. Але у випадку з об'єктом, який визначає оператор ++, речі відрізняються.

Оператор ++ (int) функція повинна створити копію. Це тому, що очікується, що постфікс ++ поверне інше значення, ніж те, що він містить: він повинен утримувати своє значення в змінній temp, збільшувати його значення і повертати temp. У випадку оператора ++ (), префікса ++, не потрібно створювати копію: об’єкт може збільшувати себе, а потім просто повертати себе.

Ось ілюстрація цього пункту:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

Кожен раз, коли ви зателефонуєте до оператора ++ (int), ви повинні створити копію, і компілятор нічого не може з цим зробити. Якщо вам надано вибір, використовуйте оператор ++ (); таким чином ви не зберігаєте копію. Це може бути значним у випадку багатьох приростів (великий цикл?) Та / або великих об'єктів.


2
"Оператор попереднього збільшення вводить залежність даних у код: ЦП повинен дочекатися завершення операції приросту, перш ніж його значення можна буде використовувати в виразі. На глибоко конвеєрному процесорі це вводить стійку. Залежність даних не існує. для оператора збільшення посади. " ( Архітектура ігрового двигуна (2-е видання) ) Отже, якщо копія посилення публікації не обчислювально обчислюється, вона все одно може перевищити попередній приріст.
Маттіас

У коді постфіксу, як це працює. C t(*this); ++(*this); return t;У другому рядку ви збільшуєте цей покажчик праворуч, і як tоновиться, якщо ви збільшуєте це. Чи не були вже скопійовані значення цього t?
rasen58

The operator++(int) function must create a copy.Ні, це не. Не більше копій ніжoperator++()
Северин Паппадо

47

Ось орієнтир для випадку, коли оператори приросту знаходяться в різних одиницях перекладу. Компілятор з g ++ 4.5.

На даний момент ігноруйте проблеми стилю

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

O (n) приріст

Тест

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Результати

Результати (терміни в секундах) з g ++ 4.5 на віртуальній машині:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

Приріст (1)

Тест

Тепер візьмемо такий файл:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Це не робить нічого в прирості. Це імітує випадок, коли прирощення має постійну складність.

Результати

Зараз результати дуже відрізняються:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

Висновок

Продуктивність

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

Семантично-мудрий

  • i++каже increment i, I am interested in the previous value, though.
  • ++iкаже increment i, I am interested in the current valueабо increment i, no interest in the previous value. Знову ви звикнете, навіть якщо ви зараз не правий.

Кнут.

Передчасна оптимізація - корінь усього зла. Як і передчасна песимізація.


1
Цікаве випробування. Зараз, майже через два з половиною роки, gcc 4.9 та Clang 3.4 демонструють подібну тенденцію. Кланг трохи швидший з обома, але невідповідність між pre та postfix є гіршим, ніж gcc.
жувати шкарпетки

Що я хотів би побачити, це приклад у реальному світі, коли ++ i / i ++ має значення. Наприклад, чи має значення будь-який із ітераторів std?
Якоб Шоу Йенсен

@JakobSchouJensen: Це були цілком призначені приклади реального світу. Розглянемо велике застосування зі складними структурами дерев (наприклад, kd-дерева, квадра-дерева) або великими контейнерами, що використовуються в шаблонах виразів (з метою максимальної пропускної здатності даних на апаратному забезпеченні SIMD). Якщо це там має значення, я не дуже впевнений, чому можна відмовлятися від посилення для конкретних випадків, якщо це не потрібно семантично.
Себастьян Мах

@phresnel: Я не думаю, що оператор ++ у вашому повсякденному шаблоні виразів - у вас є фактичний приклад цього? Типове використання оператора ++ - на цілі числа та ітератори. Це я думаю, що було б цікаво дізнатися, чи є різниця (немає різниці в цілих числах звичайно - але ітераторів).
Jakob Schou Jensen

@JakobSchouJensen: Ніякого фактичного прикладу для бізнесу, але деякі програми для скорочення кількості, де ви рахуєте речі. Ітератори Wrt, розгляньте промінь-промінь, написаний у ідіоматичному стилі C ++, і у вас є ітератор для обходу першої глибини, такий, що for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }не маєте на увазі реальну структуру дерева (BSP, kd, Quadtree, Octree Grid тощо). Такий итератор повинен був би підтримувати яке - то стан, наприклад parent node, child node, indexтощо. Загалом, моя позиція є, навіть якщо існує лише кілька прикладів, ...
Себастьян Мах,

20

Не зовсім коректно сказати, що компілятор не може оптимізувати тимчасову копію змінної у випадку postfix. Швидкий тест з VC показує, що він, принаймні, може це зробити в певних випадках.

У наступному прикладі згенерований код є ідентичним, наприклад, для префікса та постфіксу:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

Якщо ви робите ++ testFoo чи testFoo ++, ви все одно отримаєте той самий отриманий код. Насправді, не читаючи рахунку у користувача, оптимізатор все це зводив до постійної. Отже це:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

Отримано в наступному:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

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


8
Ви забули відзначити важливий момент, що тут все накреслено. Якщо визначення операторів недоступне, копії, зробленої в позалінійному коді, не уникнути; з вбудованим оптимом цілком очевидно, тому будь-який компілятор зробить це.
Blaisorblade

14

В Google C ++ Style Guide каже:

Підвищення та попереднє посилення

Використовуйте форму префікса (++ i) операторів збільшення та зменшення з ітераторами та іншими об'єктами шаблону.

Визначення: Коли змінна збільшується (++ i або i ++) або зменшується (--i або i--) і значення виразу не використовується, слід вирішити, чи слід попередньо збільшувати (декремент) чи постінкремент (декремент).

Плюси: Коли значення повернення ігнорується, форма "до" (++ i) ніколи не є менш ефективною, ніж форма "пост" (i ++), і часто є більш ефективною. Це відбувається тому, що для посилення (або зменшення) потрібна копія i, яка є значенням виразу. Якщо я є ітератором чи іншим не скалярним типом, копіювання я могло б бути дорогим. Оскільки два типи приросту поводяться однаково, коли значення ігнорується, чому б не просто завжди попередньо збільшувати?

Мінуси: В Створена традиція використовувати пост-приріст, коли значення виразу не використовується, особливо для циклів. Дехто вважає, що посткраст легше читати, оскільки "тема" (i) передує "дієслову" (++), як і англійською мовою.

Рішення: Для простих скалярних (не об’єктних) значень немає причин віддавати перевагу одній формі, і ми допускаємо будь-яку. Для ітераторів та інших типів шаблонів використовуйте попередній приріст.


1
"Рішення. Для простих скалярних (не об'єктних) значень немає причин віддавати перевагу одній формі, і ми допускаємо будь-яку. Для ітераторів та інших типів шаблонів використовуйте попереднє збільшення."
Nosredna

2
Е, ..., і що це щось?
Себастьян Мах

Зазначена посилання у відповіді в даний час зламана
Кароль

4

Я хотів би відзначити чудовий пост Ендрю Коніга на Code Talk зовсім недавно.

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

У нашій компанії ми також використовуємо конвенцію ++ iter для послідовності та продуктивності, де це можливо. Але Ендрю підкреслює переглянуті деталі щодо намірів та ефективності. Бувають випадки, коли ми хочемо використовувати iter ++ замість ++ iter.

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



4

@Ketan

... піднімає завищені деталі щодо намірів та виконання. Бувають випадки, коли ми хочемо використовувати iter ++ замість ++ iter.

Очевидно, що пост і попередній приріст мають різну семантику, і я впевнений, що всі згодні, що коли використовується результат, ви повинні використовувати відповідного оператора. Я думаю, питання полягає в тому, що робити, коли результат відкидається (як у forциклі). Відповідь на це запитання (IMHO) полягає в тому, що, оскільки міркування щодо ефективності в кращому випадку незначні, вам слід зробити те, що є більш природним. Для мене ++iбільш природно, але мій досвід говорить про те, що я в меншості, і використання i++меншої кількості металевих накладних витрат для більшості людей, які читають ваш код.

Зрештою, це причина, яку мову не називають " ++C". [*]

[*] Вставте обов’язкову дискусію про ++Cте, щоб бути більш логічним ім'ям.


4
@Motti: (жартує) Ім'я C ++ є логічним, якщо згадати Bjarne Stroustrup C ++, спочатку кодував його як попередній компілятор, що генерує програму C. Отже, C ++ повернув старе значення C. Або можливо посилити те, що C ++ є дещо концептуально хибним з самого початку.
kriss

4
  1. ++ i - швидше, не використовуючи поверненого значення
  2. i ++ - швидше використання зворотного значення

Якщо не використовується повернене значення, компілятор гарантовано не використовує тимчасовий у випадку ++ i . Не гарантовано швидше, але гарантовано не повільніше.

При використанні зворотного значення i ++ дозволяє процесору проштовхувати як приріст, так і ліву сторону в трубопровід, оскільки вони не залежать один від одного. ++ Я можу зупинити конвеєр, тому що процесор не може запустити ліву сторону, поки операція попереднього збільшення не буде змінена до кінця. Знову ж таки, застій трубопроводу не гарантується, оскільки процесор може знайти інші корисні речі, до яких слід приклеїтись.


3

Марк: Просто хотілося зазначити, що оператори ++ є хорошими кандидатами, які слід накреслити, і якщо компілятор вирішить це зробити, зайва копія буде усунена в більшості випадків. (наприклад, типи POD, якими зазвичай є ітератори.)

Однак, у більшості випадків все-таки краще використовувати стиль ++. :-)


3

Різниця в продуктивності між ++iі i++стане більш очевидною, якщо ви вважаєте операторів функціями, що повертають цінність, та способами їх реалізації. Щоб полегшити розуміння того, що відбувається, наступні приклади коду використовуватимуть так, intяк ніби це struct.

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

int& int::operator++() { 
     return *this += 1;
}

Але того ж не можна сказати i++.

Посткрімент, i++часто сприймається як повернення початкового значення перед збільшенням. Однак функція може повернути результат лише після її завершення . У результаті виникає необхідність створити копію змінної, що містить вихідне значення, збільшити змінну, а потім повернути копію, що містить початкове значення:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

Коли немає функціональної різниці між попереднім збільшенням і після збільшення, компілятор може виконати оптимізацію таким чином, щоб між ними не було різниці в продуктивності. Однак якщо задіяний такий тип даних, як a structабо class, конструктор копій буде викликаний після інкременту, і неможливо здійснити цю оптимізацію, якщо потрібна глибока копія. Таким чином, попередній приріст, як правило, швидше і вимагає менше пам'яті, ніж після збільшення.


1

@Mark: Я видалив попередню відповідь, тому що вона була трохи перевернута, і заслужила знищення лише для цього. Я насправді думаю, що це гарне запитання в тому сенсі, що він запитує, що на розум багатьох людей.

Звичайна відповідь полягає в тому, що ++ i швидше, ніж i ++, і, без сумніву, це так, але більш важливим питанням є "коли вам слід піклуватися?"

Якщо частка часу процесора, витраченого на збільшення ітераторів, становить менше 10%, то вас може не хвилювати.

Якщо частка часу процесора, витраченого на збільшення ітераторів, перевищує 10%, ви можете подивитися, які заяви роблять цю ітерацію. Подивіться, чи можна просто збільшувати цілі числа, а не використовувати ітератори. Швидше за все, ви можете, і хоча це може бути в деякому сенсі менш бажаним, швидше за все, ви заощадите весь час, проведений у цих ітераторах.

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


1

@wilhelmtell

Компілятор може схилити тимчасове. Дослівний з іншої теми:

Компілятору C ++ дозволено елімінувати тимчасові бази даних, навіть якщо це змінює поведінку програми. MSDN-посилання для VC 8:

http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx


1
Це не актуально. NRVO уникає необхідності копіювати t у "CC :: operator ++ (int)" назад на абонент, але i ++ все одно буде копіювати старе значення в стек абонента. Без NRVO i ++ створює 2 копії, одну до t і одну назад для абонента.
Blaisorblade

0

Причиною, чому ви повинні використовувати ++ i навіть у вбудованих типах, де немає переваги в продуктивності, - це створити собі хорошу звичку.


3
Вибачте, але це мене турбує. Хто каже, що це "добра звичка", коли це майже ніколи не має значення? Якщо люди хочуть зробити це частиною своєї дисципліни, це добре, але давайте відмежуємо суттєві причини від питань особистого смаку.
Майк Данлаве

@MikeDunlavey гаразд, тож якою стороною ви зазвичай користуєтесь, коли це не має значення? xD це або одне, або інше, чи не так! публікація ++ (якщо ви використовуєте її із загальним значенням. оновіть її, поверніть стару) повністю поступається ++ попередньо (оновіть її, поверніть). Ніколи не виникає причин, щоб ви хотіли мати меншу ефективність. у випадку, коли ви хочете оновити його після, програміст навіть тоді навіть не робитиме повідомлення ++. не витрачаючи часу на копіювання, коли у нас його вже є. оновити його після використання. то компілятори, що мають здоровий глузд, якого ви хотіли.
Калюжа

@Puddle: Коли я чую це: "Ніколи не виникає причин, щоб ти хотів мати меншу продуктивність" Я знаю, що я чую "копійки мудрі - дурний фунт". Вам потрібно оцінити масштаби. Тільки якщо на це припадає більше 1% залученого часу, ви навіть повинні задуматися. Зазвичай, якщо ви думаєте про це, є мільйон разів більше проблем ви НЕ розглядаєте, і це те , що робить програмне забезпечення набагато повільніше , ніж це могло б бути.
Майк Данлаве

@MikeDunlavey відремонтував дурниці, щоб задовольнити своє его. ти намагаєшся звучати як якийсь мудрий монах, але ти нічого не кажеш. величини, що беруть участь ... якби тільки понад 1% часу, який вам слід буде турбувати ... xD абсолютний дриблінг. якщо це неефективно, варто знати про це і виправити. ми тут розмірковуємо про це саме з цієї причини! ми не стурбовані тим, скільки ми можемо отримати від цих знань. і коли я сказав, що ти не хочеш меншої продуктивності, продовжуй пояснювати тоді один проклятий сценарій. MR WISE!
Калюжа

0

Обидва настільки ж швидкі;) Якщо ви хочете, що це однаковий розрахунок для процесора, це лише той порядок, в якому це робиться, що відрізняються.

Наприклад, наступний код:

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

Складіть наступну збірку:

 0x0000000100000f24 <main+0>: push   %rbp
 0x0000000100000f25 <main+1>: mov    %rsp,%rbp
 0x0000000100000f28 <main+4>: movl   $0x0,-0x4(%rbp)
 0x0000000100000f2f <main+11>:    incl   -0x4(%rbp)
 0x0000000100000f32 <main+14>:    movl   $0x0,-0x8(%rbp)
 0x0000000100000f39 <main+21>:    incl   -0x8(%rbp)
 0x0000000100000f3c <main+24>:    mov    $0x0,%eax
 0x0000000100000f41 <main+29>:    leaveq 
 0x0000000100000f42 <main+30>:    retq

Ви бачите, що для ++ та b ++ це мнемонічна величина, тому це та сама операція;)


Це C, тоді як ОП запитував C ++. У С це те саме. У C ++ швидше ++ i; завдяки своєму об’єкту. Однак деякі компілятори можуть оптимізувати оператор після збільшення.
Wiggler Jtag

0

Задане питання стосувалося того, коли результат не використовується (це зрозуміло з питання для C). Хтось може це виправити, оскільки питання "вікі спільноти"?

Про передчасні оптимізації часто згадується Кнут. Це вірно. але Дональд Кнут ніколи б не захищав той жахливий код, який ви можете побачити в наші дні. Коли-небудь бачив a = b + c серед Java Integers (не int)? Це становить 3 перетворення боксу / розпакування. Важливо уникати подібних речей. І марно писати i ++ замість ++ i - це та сама помилка. EDIT: Оскільки френель гарно зазначає це в коментарі, це можна підсумувати як "передчасна оптимізація - це зло, як і передчасна песимізація".

Навіть той факт, що люди більше звикли до i ++, - це нещасна спадщина C, спричинена концептуальною помилкою K&R (якщо ви дотримуєтесь аргументу наміру, це логічний висновок; і захищати K&R, оскільки вони K&R - безглуздо, вони чудово, але вони не великі як дизайнери мови; існує безліч помилок у дизайні C, починаючи від get () до strcpy (), до API strncpy () (у нього повинен був бути API strlcpy () з 1 дня) ).

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


Я бачу, ви працюєте над доктором наук. з інтересом до оптимізації компілятора та подібних речей. Це чудово, але не забувайте, що академія - це ехокамера, і здоровий глузд часто залишається за дверима, принаймні, у CS. Вас може зацікавити це: stackoverflow.com/questions/1303899/…
Майк Данлаве

Я ніколи не знаходив ++iбільше дратівливих, ніж i++(насправді, я вважаю, що це крутіше), але решта вашого допису отримує моє повне визнання. Можливо, додамо пункт "передчасна оптимізація - це зло, як і передчасна песимізація"
Себастьян Мах,

strncpyслугував меті у файлових системах, якими вони користувались у той час; ім'я файлу було 8-символьним буфером, і його не потрібно було закінчувати нуль. Ви не можете звинувачувати їх у тому, що не бачили 40 років у майбутньому еволюції мови.
ММ

@MattMcNabb: Чи не було 8-символьних імен файлів MS-DOS ексклюзивним? C був винайдений з Unix. У будь-якому випадку, навіть якщо strncpy мав точку, відсутність strlcpy не була повністю виправданою: навіть оригінальний C мав масиви, які ви не повинні переповнювати, для чого потрібен strlcpy; щонайбільше, вони були лише зниклими зловмисниками, які мали намір скористатися помилками. Але не можна сказати, що прогнозування цієї проблеми було тривіальним, тому якби я переписав свою посаду, я б не використовував той самий тон.
Blaisorblade

@Blaisorblade: Наскільки я пам'ятаю, перші назви файлів UNIX мали обмеження до 14 символів. Відсутність strlcpy()була виправдана тим, що вона ще не була винайдена.
Кіт Томпсон

-1

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

В основному, хитрість полягає у використанні хелперного класу, щоб відкласти приріст після повернення, і на допомогу приходить RAII

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

Винайдено для деяких важких спеціальних кодів ітераторів, і це скорочує час роботи. Вартість префікса vs postfix - це одна орієнтир, і якщо це звичайний оператор, який робить важкі переміщення, префікс і постфікс дають однаковий час для мене.


-5

++iшвидше, ніж i++тому, що він не повертає стару копію значення.

Це також більш інтуїтивно зрозуміло:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

Цей приклад C надрукує "02" замість "12", на який ви можете очікувати:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

Те саме для C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.