Есть ли разница в производительности между i ++ и ++ i в C ++?

352

У нас есть вопрос, есть ли разница в производительности между i++и ++i в C ?

Какой ответ для C ++?

Марк Харрисон
источник
Я сделал пометку, поскольку эти два тега - самый простой способ найти вопросы такого рода. Я также прошел через других, у которых не было связанных тегов, и дал им связанные теги.
Джордж Стокер
104
Есть ли разница в производительности между использованием C ++ и ++ C?
new123456
2
Статья: Разумно ли использовать префиксный оператор приращения ++ it вместо постфиксного оператора it ++ для итераторов? - viva64.com/ru/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 нет побочных эффектов.
Zan Lynx
5
@kriss: разница между C и C ++ заключается в том, что в C у вас есть гарантия, что оператор будет встроен, и в этот момент достойный оптимизатор сможет устранить разницу; вместо этого в C ++ нельзя предполагать встраивание - не всегда.
Blaisorblade
3
Я бы +1, если бы ответ упомянул что-то о классах, которые содержат указатели (будь то авто, умные или примитивные) на динамически выделяемую (кучу) память, где конструктор копирования обязательно выполняет глубокие копии. В таких случаях аргумент отсутствует, ++ i, возможно, на порядок эффективнее i ++. Ключевой момент заключается в том, чтобы привыкнуть использовать преинкремент, когда семантика постинкремента фактически не требуется вашим алгоритмом, и тогда вы будете иметь привычку писать код, который по своей природе поддается большей эффективности, независимо от того, как ну ваш компилятор может оптимизировать.
телефонная метка
64

Да. Там есть.

Оператор ++ может быть или не быть определен как функция. Для примитивных типов (int, double, ...) операторы встроены, поэтому компилятор, вероятно, сможет оптимизировать ваш код. Но в случае объекта, который определяет оператор ++, все иначе.

Функция operator ++ (int) должна создавать копию. Это связано с тем, что postfix ++ должен возвращать значение, отличное от того, что он содержит: он должен хранить свое значение в переменной 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
}

Каждый раз, когда вы вызываете operator ++ (int), вы должны создавать копию, и компилятор ничего не может с этим поделать. Когда предоставляется выбор, используйте оператор ++ (); Таким образом, вы не сохраняете копию. Это может быть важно в случае многих приращений (большой цикл?) И / или больших объектов.

wilhelmtell
источник
2
«Оператор предварительного приращения вводит зависимость данных в коде: ЦП должен дождаться завершения операции приращения, прежде чем его значение может быть использовано в выражении. На глубоко конвейерном ЦП это приводит к остановке. Зависимости от данных нет для оператора увеличения поста. " ( Архитектура игрового движка (2-е издание) ) Поэтому, если копия постинкремента не требует большого объема вычислений, она все равно может превзойти предварительный инкремент.
Матиас
В постфиксном коде, как это работает? C t(*this); ++(*this); return t;Во второй строке вы увеличиваете указатель this вправо, поэтому как 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

O (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 показывают аналогичную тенденцию. Clang немного быстрее с обоими, но несоответствие между pre и postfix хуже, чем gcc.
жевать носки
То, что я действительно хотел бы видеть, является реальным примером, где ++ i / i ++ имеет значение. Например, имеет ли это значение на любом из итераторов std?
Якоб Шоу Дженсен
@JakobSchouJensen: Это были довольно реальные примеры. Рассмотрим большое приложение со сложными древовидными структурами (например, kd-деревьями, quad-деревьями) или большими контейнерами, используемыми в шаблонах выражений (чтобы максимизировать пропускную способность данных на оборудовании SIMD). Если это что-то меняет, я не совсем уверен, почему можно было бы перейти к постинкременту для конкретных случаев, если это не нужно семантически.
Себастьян Мах
@phresnel: Я не думаю, что operator ++ в вашем повседневном использовании является шаблоном выражения. У вас есть реальный пример этого? Типичное использование оператора ++ для целых чисел и итераторов. Это было бы, я думаю, было бы интересно узнать, есть ли какая-либо разница (конечно, нет разницы в целых числах - кроме итераторов).
Якоб Шоу Дженсен
@JakobSchouJensen: Нет реального бизнес-примера, но есть несколько хрустящих приложений, где вы считаете вещи. В отношении итераторов рассмотрим трассировку лучей, написанную в идиоматическом стиле 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

Не совсем правильно говорить, что компилятор не может оптимизировать удаление временной переменной в случае постфикса. Быстрый тест с 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)] 

Таким образом, хотя постфиксная версия может быть медленнее, вполне возможно, что оптимизатор будет достаточно хорош, чтобы избавиться от временной копии, если вы ее не используете.

Джеймс Сазерленд
источник
8
Вы забыли отметить важный момент, что здесь все указано. Если определения операторов недоступны, нельзя избежать копирования, выполненного во внешнем коде; со встроенным оптимизатором вполне очевидно, так что любой компилятор сделает это.
Blaisorblade
14

В Google C ++ Style Guide говорит:

Прединкремент и предкремент

Используйте префиксную форму (++ i) операторов увеличения и уменьшения с итераторами и другими объектами шаблона.

Определение: когда переменная увеличивается (++ i или i ++) или уменьшается (--i или i--) и значение выражения не используется, необходимо решить, будет ли преинкремент (декремент) или постинкремент (декремент).

Плюсы: Когда возвращаемое значение игнорируется, форма "pre" (++ i) никогда не менее эффективна, чем форма "post" (i ++), и часто более эффективна. Это потому, что постинкремент (или декремент) требует создания копии i, которая является значением выражения. Если я итератор или другой нескалярный тип, копирование может быть дорогим. Поскольку два типа приращения ведут себя одинаково, когда значение игнорируется, почему бы просто не всегда выполнить предварительное приращение?

Минусы: в Си сложилась традиция использовать постинкремент, когда значение выражения не используется, особенно в циклах for. Некоторые считают, что постинкремент легче читать, поскольку «subject» (i) предшествует «глаголу» (++), как и в английском.

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

martjno
источник
1
«Решение: для простых скалярных (необъектных) значений нет причин предпочитать одну форму, и мы допускаем любую. Для итераторов и других типов шаблонов используйте предварительное увеличение».
Носредна
2
Эх ... а что это такое?
Себастьян Мах
Упомянутая ссылка в ответе в настоящее время не работает
Кароль
4

Я хотел бы отметить отличный пост Эндрю Кенига о Code Talk совсем недавно.

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

В нашей компании мы также используем соглашение ++ iter для согласованности и производительности, где это применимо. Но Эндрю поднимает упущенные детали относительно намерения против производительности. Иногда мы хотим использовать iter ++ вместо ++ iter.

Итак, сначала определитесь с вашим намерением, и если pre или post не имеют значения, тогда переходите к pre, поскольку это принесет некоторую выгоду производительности, избегая создания дополнительного объекта и бросая его.


источник
Обновленная ссылка: drdobbs.com/architecture-and-design/efficiency-versus-intent/…
Офек
4

@Ketan

... поднимает упущенные детали относительно намерения против производительности. Иногда мы хотим использовать iter ++ вместо ++ iter.

Очевидно, что post и pre-increment имеют разную семантику, и я уверен, что все согласны с тем, что при использовании результата вы должны использовать соответствующий оператор. Я думаю, вопрос в том, что делать, когда результат отбрасывается (как в forциклах). Ответ на этот вопрос (ИМХО) заключается в том, что, поскольку соображения производительности в лучшем случае незначительны, вы должны делать то, что более естественно. Для меня ++iэто более естественно, но мой опыт подсказывает мне, что я в меньшинстве, и использование i++приведет к снижению затрат металла для большинства людей, читающих ваш код.

Ведь именно поэтому язык не называется "++C ". [*]

[*] Включить обязательное обсуждение ++Cболее логичного имени.

Моти
источник
4
@Motti: (шутит) Имя C ++ логично, если вспомнить Bjarne Stroustrup C ++, который изначально кодировал его как прекомпилятор, генерирующий программу на C. Следовательно, C ++ вернул старое значение C. Или может быть, что C ++ с самого начала несколько концептуально несовершенен.
Крис
4
  1. ++ я - быстрее, не используя возвращаемое значение
  2. i ++ - быстрее, используя возвращаемое значение

Когда не используется возвращаемое значение, компилятор гарантированно не использует временный в случае ++ i . Не гарантируется, что будет быстрее, но гарантированно не будет медленнее.

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

Ханс Малербе
источник
3

Марк: Просто хотел бы отметить, что операторы ++ являются хорошими кандидатами для встраивания, и если компилятор решит это сделать, избыточная копия будет исключена в большинстве случаев. (например, типы POD, которые обычно являются итераторами.)

Тем не менее, в большинстве случаев все еще лучше использовать ++ iter. :-)

0124816
источник
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;
}

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

оборота DragonLord
источник
1

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

Обычный ответ таков: ++ я быстрее, чем 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 это либо один, либо другой, не так ли? post ++ (если вы используете его с общим смыслом. обновите его, верните старый) полностью уступает ++ pre (обновите его, верните), никогда не было причин, по которым вы хотели бы иметь меньшую производительность. в случае, когда вы захотите обновить его после, программист вообще не будет делать post ++. не тратить время на копирование, когда оно у нас уже есть. обновите его после того, как мы его используем. затем компиляторы, имеющие здравый смысл, который вы хотели бы иметь.
Лужа
@Puddle: Когда я слышу это: «никогда не было причин, по которым вы хотели бы иметь меньшую производительность», я знаю, что слышу «глупый пенни». Вы должны иметь представление о величине вовлеченных. Только если это составляет более 1% времени, вы должны подумать об этом. Обычно, если вы думаете об этом, есть проблемы в миллион раз больше, которые вы не рассматриваете, и это делает программное обеспечение намного медленнее, чем могло бы быть.
Майк Данлавей
@MikeDunlavey отрыгнул ерунду, чтобы удовлетворить ваше эго. ты пытаешься звучать как какой-то мудрый монах, но ничего не говоришь. вовлеченные величины ... если только в течение 1% времени вы должны заботиться ... xD абсолютный дриблинг. если это неэффективно, это стоит знать и исправить. мы здесь обдумываем это именно по этой причине! нас не волнует, сколько мы можем получить от этого знания. и когда я сказал, что вы не хотите меньше производительности, тогда объясните один чертов сценарий. Мистер Мудрый!
Лужа
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

Вы видите, что для a ++ и b ++ это мнемоника вкл, так что это та же самая операция;)

Geoffroy
источник
Это C, а OP спросил C ++. В Си тоже самое. В C ++ быстрее работает ++ i; из-за его объекта. Однако некоторые компиляторы могут оптимизировать оператор постинкремента.
Wiggler Jtag
0

Намеченный вопрос был о том, когда результат не используется (это ясно из вопроса для C). Кто-нибудь может это исправить, так как вопрос «вики сообщества»?

О преждевременной оптимизации часто цитируют Кнута. Вот так. но Дональд Кнут никогда не защитит с этим ужасным кодом, который вы можете видеть в эти дни. Вы когда-нибудь видели a = b + c среди Java Integer (не int)? Это составляет 3 конверсии в бокс / распаковку. Очень важно избегать подобных вещей. И бесполезно писать i ++ вместо ++ i - это та же ошибка. РЕДАКТИРОВАТЬ: Как хорошо говорит Френель в комментарии, это можно суммировать как «преждевременная оптимизация - это зло, так же как и преждевременная пессимизация».

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

Кстати, я один из тех, кто недостаточно привык к C ++, чтобы найти ++, которую я раздражаю читать. Тем не менее, я использую это, так как я признаю, что это правильно.

оборота Блейзорблэйд
источник
Я вижу, вы работаете над докторской диссертацией. с интересом к оптимизации компилятора и тому подобного. Это здорово, но не забывайте, что академия - это эхо-камера, и здравый смысл часто остается за дверью, по крайней мере, в CS. Вам может быть интересно это: stackoverflow.com/questions/1303899/…
Майк Данлавей,
Я никогда не находил ++iбольше раздражающего, чем i++(на самом деле, я нашел это круче), но остальная часть вашего поста получает мое полное признание. Возможно, добавьте пункт «преждевременная оптимизация - это зло, равно как и преждевременная пессимизация»
Себастьян Мах
strncpyслужил цели в файловых системах, которые они использовали в то время; имя файла было 8-символьным буфером, и оно не должно заканчиваться нулем. Вы не можете винить их за то, что они не видели 40 лет в будущем языковой эволюции.
ММ
@MattMcNabb: не было ли 8-символьное имя файла эксклюзивным для MS-DOS? Си был изобретен с 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;
}

Изобретен для некоторого тяжелого пользовательского кода итераторов, и он сокращает время выполнения. Стоимость префикса по сравнению с постфиксом теперь является одной ссылкой, и если это пользовательский оператор, выполняющий тяжелые перемещения, префикс и постфикс дают мне одинаковое время выполнения.

Северин Паппадо
источник
-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;
}
Cees Timmerman
источник