¿Hay alguna diferencia de rendimiento entre i ++ y ++ i en C ++?

352

Tenemos la pregunta ¿hay una diferencia de rendimiento entre i++y ++i en C ?

¿Cuál es la respuesta para C ++?

Mark Harrison
fuente
Volví a etiquetar ya que esas dos etiquetas son la forma más fácil de encontrar preguntas de esta naturaleza. También pasé por otros que no tenían etiquetas cohesivas y les di etiquetas cohesivas.
George Stocker
104
¿Hay alguna diferencia de rendimiento entre usar C ++ y ++ C?
nuevo123456
2
Artículo: ¿Es razonable usar el operador de incremento de prefijo ++ en lugar del operador postfix it ++ para iteradores? - viva64.com/en/b/0093

Respuestas:

426

[Resumen ejecutivo: utilícelo ++isi no tiene un motivo específico para utilizarlo i++]

Para C ++, la respuesta es un poco más complicada.

Si ies un tipo simple (no una instancia de una clase C ++), entonces la respuesta dada para C ("No, no hay diferencia de rendimiento") es válida, ya que el compilador está generando el código.

Sin embargo, si ies una instancia de una clase C ++, entonces i++y ++iestá haciendo llamadas a una de las operator++funciones. Aquí hay un par estándar de estas funciones:

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;
}

Como el compilador no genera código, sino que solo llama a una operator++función, no hay forma de optimizar la tmpvariable y su constructor de copia asociado. Si el constructor de copias es costoso, esto puede tener un impacto significativo en el rendimiento.

Mark Harrison
fuente
3
Lo que el compilador puede evitar es la segunda copia que devuelve tmp, asignando tmp en el llamador, a través de NRVO, como se menciona en otro comentario.
Blaisorblade
77
¿No puede el compilador evitar esto por completo si operator ++ está en línea?
Eduard - Gabriel Munteanu
16
Sí, si operator ++ está en línea y nunca se usa tmp, se puede quitar a menos que el constructor o destructor del objeto tmp tenga efectos secundarios.
Zan Lynx
55
@kriss: la diferencia entre C y C ++ es que en C tienes la garantía de que el operador estará en línea, y en ese momento un optimizador decente podrá eliminar la diferencia; en cambio, en C ++ no puedes asumir la alineación, no siempre.
Blaisorblade
3
Yo +1 SI la respuesta menciona algo sobre las clases que contienen punteros (ya sea automático, inteligente o primitivo) a la memoria asignada dinámicamente (montón), donde el constructor de copias necesariamente realiza copias profundas. En tales casos, no hay argumento, ++ i es quizás un orden de magnitud más eficiente que i ++. La clave es tener el hábito de usar el preincremento cada vez que su algoritmo no requiera semántica de posincremento, y luego tendrá la costumbre de escribir código que, por naturaleza, se presta a una mayor eficiencia, independientemente de cómo bien tu compilador puede optimizar.
phonetagger
64

Si. Ahi esta.

El operador ++ puede o no definirse como una función. Para los tipos primitivos (int, double, ...) los operadores están integrados, por lo que el compilador probablemente podrá optimizar su código. Pero en el caso de un objeto que define el operador ++, las cosas son diferentes.

La función operator ++ (int) debe crear una copia. Esto se debe a que se espera que postfix ++ devuelva un valor diferente al que contiene: debe mantener su valor en una variable temporal, incrementar su valor y devolver la temperatura. En el caso del operador ++ (), prefijo ++, no hay necesidad de crear una copia: el objeto puede incrementarse y luego simplemente devolverse.

Aquí hay una ilustración del punto:

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
}

Cada vez que llama al operador ++ (int) debe crear una copia, y el compilador no puede hacer nada al respecto. Cuando se le dé la opción, use operator ++ (); de esta manera no guarda una copia. Puede ser significativo en el caso de muchos incrementos (¿bucle grande?) Y / u objetos grandes.

Wilhelmtell
fuente
2
"El operador de incremento previo introduce una dependencia de datos en el código: la CPU debe esperar a que se complete la operación de incremento antes de que se pueda usar su valor en la expresión. En una CPU profundamente canalizada, esto introduce un bloqueo. No hay dependencia de datos para el operador de incremento de publicación ". ( Game Engine Architecture (2a edición) ) Entonces, si la copia de un incremento posterior no es computacionalmente intensiva, aún puede superar el incremento previo.
Matthias
En el código de postfix, ¿cómo funciona esto? C t(*this); ++(*this); return t;En la segunda línea, estás incrementando este puntero correctamente, entonces, ¿cómo tse actualiza si estás incrementando esto? ¿No se copiaron ya los valores de esto t?
rasen58
The operator++(int) function must create a copy.no, no es. No más copias queoperator++()
Severin Pappadeux
47

Aquí hay un punto de referencia para el caso cuando los operadores de incremento están en diferentes unidades de traducción. Compilador con g ++ 4.5.

Ignora los problemas de estilo por ahora

// 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) incremento

Prueba

// 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;
}

Resultados

Resultados (los tiempos son en segundos) con g ++ 4.5 en una máquina virtual:

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) incremento

Prueba

Tomemos ahora el siguiente archivo:

// 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;
}

No hace nada en el incremento. Esto simula el caso cuando el incremento tiene una complejidad constante.

Resultados

Los resultados ahora varían extremadamente:

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

Conclusión

En cuanto al rendimiento

Si no necesita el valor anterior, acostúmbrese a usar pre-incremento. Sea coherente incluso con los tipos incorporados, se acostumbrará y no correrá el riesgo de sufrir una pérdida de rendimiento innecesaria si alguna vez reemplaza un tipo integrado con un tipo personalizado.

Semántico sabio

  • i++dice increment i, I am interested in the previous value, though.
  • ++idice increment i, I am interested in the current valueo increment i, no interest in the previous value. Una vez más, te acostumbrarás, incluso si no lo estás ahora.

Knuth

La optimización prematura es la fuente de todos los males. Como es el pesimismo prematuro.

revés phresnel
fuente
1
Prueba interesante Ahora, casi dos años y medio después, gcc 4.9 y Clang 3.4 muestran una tendencia similar. Clang es un poco más rápido con ambos, pero la disparidad entre pre y postfix es peor que gcc.
masticar calcetines
Lo que realmente me gustaría ver es un ejemplo del mundo real donde ++ i / i ++ marca la diferencia. Por ejemplo, ¿hace alguna diferencia en alguno de los iteradores estándar?
Jakob Schou Jensen
@JakobSchouJensen: pretendían ser ejemplos del mundo real. Considere una aplicación grande, con estructuras de árbol complejas (por ejemplo, árboles kd, árboles cuádruples) o contenedores grandes utilizados en plantillas de expresión (para maximizar el rendimiento de datos en el hardware SIMD). Si hace una diferencia allí, no estoy realmente seguro de por qué uno recurriría al post-incremento para casos específicos si no se necesita semánticamente.
Sebastian Mach
@phresnel: No creo que operator ++ sea en su día a día una plantilla de expresión, ¿tiene un ejemplo real de esto? El uso típico de operator ++ es en enteros e iteradores. Creo que sería interesante saber si hay alguna diferencia (no hay diferencia en los enteros, por supuesto, pero en los iteradores).
Jakob Schou Jensen
@JakobSchouJensen: No hay un ejemplo comercial real, pero algunas aplicaciones de cálculo de números donde cuentas cosas. Los iteradores de Wrt, considere un trazador de rayos que está escrito en un estilo idiomático de C ++, y tiene un iterador para el recorrido de profundidad primero, de modo que for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }, no importa la estructura de árbol real (BSP, kd, Quadtree, Octree Grid, etc.). Tal iterador necesitaría mantener un estado, por ejemplo parent node, child node, indexy cosas por el estilo. En general, mi postura es, incluso si existen pocos ejemplos, ...
Sebastian Mach
20

No es del todo correcto decir que el compilador no puede optimizar la copia variable temporal en el caso de postfix. Una prueba rápida con VC muestra que, al menos, puede hacer eso en ciertos casos.

En el siguiente ejemplo, el código generado es idéntico para el prefijo y el postfix, por ejemplo:

#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());
}

Ya sea que haga ++ testFoo o testFoo ++, seguirá obteniendo el mismo código resultante. De hecho, sin leer el recuento del usuario, el optimizador redujo todo a una constante. Así que esto:

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

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

Resultó en lo siguiente:

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

Entonces, si bien es cierto que la versión de postfix podría ser más lenta, es posible que el optimizador sea lo suficientemente bueno como para deshacerse de la copia temporal si no la está usando.

James Sutherland
fuente
8
Olvidaste notar el punto importante de que aquí todo está en línea. Si las definiciones de los operadores no están disponibles, no se puede evitar la copia realizada en el código fuera de línea; con la alineación, el optim es bastante obvio, por lo que cualquier compilador lo hará.
Blaisorblade
14

La Guía de estilo de Google C ++ dice:

Preincremento y Predecremento

Utilice la forma de prefijo (++ i) de los operadores de incremento y decremento con iteradores y otros objetos de plantilla.

Definición: Cuando una variable se incrementa (++ i o i ++) o disminuye (--i o i--) y el valor de la expresión no se usa, uno debe decidir si preincremento (decremento) o postincremento (decremento).

Pros: cuando se ignora el valor de retorno, la forma "pre" (++ i) nunca es menos eficiente que la forma "post" (i ++) y, a menudo, es más eficiente. Esto se debe a que el incremento posterior (o decremento) requiere que se haga una copia de i, que es el valor de la expresión. Si soy un iterador u otro tipo no escalar, copiarlo podría ser costoso. Dado que los dos tipos de incremento se comportan de la misma manera cuando se ignora el valor, ¿por qué no siempre pre-incrementar?

Contras: La tradición se desarrolló, en C, de usar post-incremento cuando no se usa el valor de expresión, especialmente en for loops. Algunos encuentran que el incremento posterior es más fácil de leer, ya que el "asunto" (i) precede al "verbo" (++), al igual que en inglés.

Decisión: Para valores escalares simples (no objeto) no hay razón para preferir una forma y permitimos cualquiera. Para iteradores y otros tipos de plantillas, use pre-incremento.

martjno
fuente
1
"Decisión: para valores escalares simples (no objeto) no hay razón para preferir una forma y permitimos cualquiera de ellos. Para iteradores y otros tipos de plantillas, use pre-incremento".
Nosredna
2
Eh ... ¿y qué es ese algo?
Sebastian Mach
El enlace mencionado en la respuesta está actualmente roto
karol
4

Me gustaría señalar una excelente publicación de Andrew Koenig en Code Talk muy recientemente.

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

En nuestra empresa también utilizamos la convención de ++ iter para obtener consistencia y rendimiento cuando corresponda. Pero Andrew plantea detalles pasados ​​por alto con respecto a la intención frente al rendimiento. Hay momentos en los que queremos usar iter ++ en lugar de ++ iter.

Por lo tanto, primero decida su intención y si pre o post no importa, vaya con pre, ya que tendrá algún beneficio de rendimiento al evitar la creación de objetos adicionales y arrojarlos.


fuente
4

@Ketan

... plantea detalles pasados ​​por alto con respecto a la intención frente al rendimiento. Hay momentos en los que queremos usar iter ++ en lugar de ++ iter.

Obviamente, post y pre-incremento tienen semánticas diferentes y estoy seguro de que todos están de acuerdo en que cuando se usa el resultado, debe usar el operador apropiado. Creo que la pregunta es qué debería hacer uno cuando se descarta el resultado (como en los forbucles). La respuesta a esta pregunta (en mi humilde opinión) es que, dado que las consideraciones de rendimiento son insignificantes en el mejor de los casos, debe hacer lo que es más natural. Para mí ++ies más natural, pero mi experiencia me dice que soy minoritario y que el uso i++causará menos gastos generales de metal para la mayoría personas que leen su código.

Después de todo, esa es la razón por la cual el idioma no se llama " ++C". [*]

[*] Insertar discusión obligatoria sobre ++Cser un nombre más lógico.

Motti
fuente
44
@Motti: (bromeando) El nombre de C ++ es lógico si recuerdas que Bjarne Stroustrup C ++ lo codificó inicialmente como un precompilador que genera un programa en C. Por lo tanto, C ++ devolvió un antiguo valor de C. O puede ser para mejorar que C ++ tiene defectos conceptuales desde el principio.
kriss
4
  1. ++ i - más rápido sin usar el valor de retorno
  2. i ++ - más rápido usando el valor de retorno

Cuando no se utiliza el valor de retorno, se garantiza que el compilador no utilizará un temporal en el caso de ++ i . No se garantiza que sea más rápido, pero se garantiza que no sea más lento.

Cuando se usa el valor de retorno, i ++ permite que el procesador empuje tanto el incremento como el lado izquierdo hacia la tubería, ya que no dependen el uno del otro. ++ Puedo detener la canalización porque el procesador no puede iniciar el lado izquierdo hasta que la operación de preincremento haya recorrido todo el camino. Una vez más, no se garantiza un bloqueo de la tubería, ya que el procesador puede encontrar otras cosas útiles para quedarse.

Hans Malherbe
fuente
3

Mark: Solo quería señalar que los operadores ++ son buenos candidatos para estar en línea, y si el compilador decide hacerlo, la copia redundante se eliminará en la mayoría de los casos. (por ejemplo, tipos de POD, que suelen ser los iteradores).

Dicho esto, todavía es mejor usar ++ iter en la mayoría de los casos. :-)

0124816
fuente
3

La diferencia de rendimiento entre ++iy i++será más evidente cuando piense en los operadores como funciones de retorno de valor y cómo se implementan. Para facilitar la comprensión de lo que está sucediendo, los siguientes ejemplos de código se usarán intcomo si fuera un struct.

++iincrementa la variable, luego devuelve el resultado. Esto se puede hacer en el lugar y con un tiempo mínimo de CPU, requiriendo solo una línea de código en muchos casos:

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

Pero no se puede decir lo mismo i++.

El incremento posterior i++, a menudo se considera que devuelve el valor original antes de incrementar. Sin embargo, una función solo puede devolver un resultado cuando está terminada . Como resultado, se hace necesario crear una copia de la variable que contiene el valor original, incrementar la variable y luego devolver la copia que contiene el valor original:

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

Cuando no hay una diferencia funcional entre el pre-incremento y el post-incremento, el compilador puede realizar una optimización tal que no exista una diferencia de rendimiento entre los dos. Sin embargo, si se trata de un tipo de datos compuestos como a structo class, se llamará al constructor de la copia en el post-incremento y no será posible realizar esta optimización si se necesita una copia profunda. Como tal, el incremento previo generalmente es más rápido y requiere menos memoria que el incremento posterior.

DragonLord
fuente
1

@ Mark: eliminé mi respuesta anterior porque era un poco flip, y merecía un voto negativo solo por eso. De hecho, creo que es una buena pregunta en el sentido de que pregunta qué piensa mucha gente.

La respuesta habitual es que ++ i es más rápido que i ++, y sin duda lo es, pero la pregunta más importante es "¿cuándo debería importarle?"

Si la fracción del tiempo de CPU invertido en los iteradores incrementales es inferior al 10%, es posible que no le importe.

Si la fracción del tiempo de CPU invertido en incrementos de iteradores es mayor al 10%, puede ver qué declaraciones están haciendo esa iteración. Vea si podría simplemente incrementar los enteros en lugar de usar iteradores. Lo más probable es que pueda, y aunque en cierto sentido puede ser menos deseable, es muy probable que ahorre esencialmente todo el tiempo que pase en esos iteradores.

He visto un ejemplo en el que el incremento de iterador consumía más del 90% del tiempo. En ese caso, ir al incremento de números enteros redujo esencialmente el tiempo de ejecución en esa cantidad. (es decir, mejor que 10x de aceleración)

Mike Dunlavey
fuente
1

@wilhelmtell

El compilador puede eludir lo temporal. Verbatim del otro hilo:

El compilador de C ++ puede eliminar temporarios basados ​​en la pila, incluso si al hacerlo cambia el comportamiento del programa. Enlace MSDN para VC 8:

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

Mat Noguchi
fuente
1
Eso no es relevante. NRVO evita la necesidad de copiar t en "CC :: operator ++ (int)" de nuevo a la persona que llama, pero i ++ seguirá copiando el valor anterior en la pila de la persona que llama. Sin NRVO, i ++ crea 2 copias, una para t y otra para la persona que llama.
Blaisorblade
0

Y la razón por la que deberías usar ++ i incluso en tipos integrados donde no hay ventaja de rendimiento es crear un buen hábito para ti.

Josh
fuente
3
Lo siento, pero eso me molesta. ¿Quién dice que es un "buen hábito", cuando casi nunca importa? Si la gente quiere que sea parte de su disciplina, está bien, pero distingamos razones importantes de cuestiones de gusto personal.
Mike Dunlavey, el
@MikeDunlavey ok, entonces, ¿qué lado usas normalmente cuando no importa? xD es uno o el otro, ¿no? el post ++ (si lo está utilizando con el significado general. actualícelo, devuelva el anterior) es completamente inferior a ++ pre (actualícelo, devuélvalo) nunca hay ninguna razón por la que desee tener menos rendimiento. en el caso de que desee actualizarlo después, el programador ni siquiera hará la publicación ++ en absoluto. sin perder tiempo copiando cuando ya lo tenemos. actualízalo después de que lo usemos. entonces los compiladores tienen el sentido común que querías que tuviera.
Charco
@Puddle: Cuando escucho esto: "nunca hay ninguna razón por la que quieras tener menos rendimiento" Sé que estoy escuchando "centavo - libra tonto". Necesita tener una apreciación de las magnitudes involucradas. Solo si esto representa más del 1% del tiempo involucrado, incluso debería pensarlo. Por lo general, si está pensando en esto, hay problemas millones de veces más grandes que no está considerando, y esto es lo que hace que el software sea mucho más lento de lo que podría ser.
Mike Dunlavey
@MikeDunlavey regurgitó tonterías para satisfacer tu ego. intentas sonar como un monje sabio, pero no dices nada. las magnitudes involucradas ... aunque solo sea más del 1% del tiempo que debería importarle ... xD regate absoluto. Si es ineficiente, vale la pena conocerlo y solucionarlo. ¡Estamos aquí reflexionando sobre esto por esa razón exacta! no nos preocupa cuánto podemos ganar de este conocimiento. y cuando dije que no querrías menos rendimiento, adelante, explica un maldito escenario entonces. Señor sabio!
Charco
0

Ambos son tan rápidos;) Si lo desea, es el mismo cálculo para el procesador, es solo el orden en el que se hace lo que difiere.

Por ejemplo, el siguiente código:

#include <stdio.h>

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

Produzca el siguiente ensamblaje:

 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

Verá que para a ++ y b ++ es un mnemónico incluido, por lo que es la misma operación;)

Geoffroy
fuente
Es C, mientras que OP le preguntó a C ++. En C es lo mismo. En C ++, el más rápido es ++ i; debido a su objeto. Sin embargo, algunos compiladores pueden optimizar el operador posterior al incremento.
Wiggler Jtag
0

La pregunta que se pretendía era cuándo no se utiliza el resultado (eso queda claro de la pregunta para C). ¿Alguien puede arreglar esto ya que la pregunta es "wiki de la comunidad"?

Acerca de las optimizaciones prematuras, a menudo se cita a Knuth. Así es. pero Donald Knuth nunca defendería con ese código horrible que puedes ver en estos días. ¿Alguna vez has visto a = b + c entre enteros Java (no int)? Eso equivale a 3 conversiones de boxeo / unboxing. Evitar cosas como esas es importante. Y escribir inútilmente i ++ en lugar de ++ i es el mismo error. EDITAR: Como el phresnel lo pone muy bien en un comentario, esto se puede resumir como "la optimización prematura es mala, como lo es la pesimización prematura".

Incluso el hecho de que las personas estén más acostumbradas a i ++ es un desafortunado legado en C, causado por un error conceptual de K&R (si sigues el argumento de la intención, esa es una conclusión lógica; y defender K&R porque son K&R no tiene sentido, son genial, pero no son excelentes como diseñadores de lenguaje; existen innumerables errores en el diseño de C, que van desde gets () a strcpy (), a la API strncpy () (debería haber tenido la API strlcpy () desde el día 1) )

Por cierto, soy uno de los que no estoy lo suficientemente acostumbrado a C ++ para encontrar ++ i molesto para leer. Aún así, lo uso ya que reconozco que es correcto.

revs Blaisorblade
fuente
Veo que estás trabajando en un doctorado. con interés en la optimización del compilador y cosas por el estilo. Eso está muy bien, pero no se olvide de la academia es una caja de resonancia, y el sentido común a menudo se queda fuera de la puerta, por lo menos en CS que podría estar interesado en esto: stackoverflow.com/questions/1303899/...
Mike Dunlavey
Nunca me pareció ++imás molesto que i++(de hecho, lo encontré más fresco), pero el resto de tu publicación recibe mi reconocimiento completo. Tal vez agregue un punto "la optimización prematura es malvada, como lo es la pesimismo prematura"
Sebastian Mach
strncpycumplió un propósito en los sistemas de archivos que estaban usando en ese momento; el nombre del archivo era un búfer de 8 caracteres y no tenía que ser anulado. No se les puede culpar por no ver 40 años en el futuro de la evolución del lenguaje.
MM
@MattMcNabb: ¿el nombre de archivo de 8 caracteres no era exclusivo de MS-DOS? C fue inventado con Unix. De todos modos, incluso si strncpy tenía un punto, la falta de strlcpy no estaba completamente justificada: incluso C original tenía matrices que no debería desbordar, que necesitaban strlcpy; a lo sumo, solo les faltaban atacantes con la intención de explotar los errores. Pero no se puede decir que pronosticar este problema sea trivial, por lo que si reescribiera mi publicación no usaría el mismo tono.
Blaisorblade
@Blaisorblade: Según recuerdo, los primeros nombres de archivos UNIX estaban limitados a 14 caracteres. La falta de strlcpy()fue justificada por el hecho de que aún no se había inventado.
Keith Thompson
-1

Es hora de proporcionar gemas de sabiduría a las personas;): hay un simple truco para hacer que el incremento de postfix de C ++ se comporte casi igual que el incremento de prefijo (lo inventé para mí, pero también lo vi en el código de otras personas, así que no estoy solo).

Básicamente, el truco es usar la clase auxiliar para posponer el incremento después del regreso, y RAII viene a rescatar

#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;
}

Inventado es para algunos códigos de iteradores personalizados pesados, y reduce el tiempo de ejecución. El costo del prefijo vs postfix es una referencia ahora, y si este es un operador personalizado que se mueve mucho, el prefijo y el postfix me dieron el mismo tiempo de ejecución.

Severin Pappadeux
fuente
-5

++ies más rápido que i++porque no devuelve una copia antigua del valor.

También es más intuitivo:

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

Este ejemplo de C imprime "02" en lugar del "12" que podría esperar:

#include <stdio.h>

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

Lo mismo para C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Cees Timmerman
fuente