C'è una differenza di prestazioni tra i ++ e ++ i in C ++?


352

Abbiamo la domanda : esiste una differenza di prestazioni tra i++e ++i in C ?

Qual è la risposta per C ++?


Ho ricodificato poiché quei due tag sono il modo più semplice per trovare domande di questo tipo. Ho anche esaminato altri che non avevano tag coesivi e ho dato loro tag coerenti.
George Stocker,

104
C'è una differenza di prestazioni tra l'utilizzo di C ++ e ++ C?
nuovo123456,

2
Articolo: È ragionevole usare l'operatore di incremento prefisso ++ it invece dell'operatore postfix it ++ per gli iteratori? - viva64.com/it/b/0093

Risposte:


426

[Riepilogo esecutivo: utilizzare ++ise non si dispone di un motivo specifico da utilizzare i++.]

Per C ++, la risposta è un po 'più complicata.

Se iè un tipo semplice (non un'istanza di una classe C ++), vale la risposta data per C ("No non c'è differenza di prestazioni") , poiché il compilatore sta generando il codice.

Tuttavia, se iè un'istanza di una classe C ++, quindi i++e ++ista effettuando chiamate a una delle operator++funzioni. Ecco una coppia standard di queste funzioni:

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

Poiché il compilatore non sta generando codice, ma sta semplicemente chiamando una operator++funzione, non c'è modo di ottimizzare la tmpvariabile e il suo costruttore di copie associato. Se il costruttore di copie è costoso, ciò può avere un impatto significativo sulle prestazioni.


3
Ciò che il compilatore può evitare è la seconda copia che restituisce tmp, allocando tmp nel chiamante, tramite NRVO, come menzionato da un altro commento.
Blaisorblade,

7
Il compilatore non può evitarlo del tutto se operator ++ è inline?
Eduard - Gabriel Munteanu,

16
Sì, se l'operatore ++ è inline e tmp non viene mai utilizzato, può essere rimosso a meno che il costruttore o il distruttore dell'oggetto tmp non abbia effetti collaterali.
Zan Lynx,

5
@kriss: la differenza tra C e C ++ è che in C hai la garanzia che l'operatore sarà integrato e a quel punto un ottimizzatore decente sarà in grado di rimuovere la differenza; invece in C ++ non puoi assumere inline - non sempre.
Blaisorblade,

3
Farei +1 se la risposta menzionasse qualcosa sulle classi che contengono puntatori (sia automatici, intelligenti o primitivi) alla memoria allocata dinamicamente (heap), dove il costruttore di copie esegue necessariamente copie profonde. In tali casi, non vi è alcun argomento, ++ i è forse un ordine di grandezza più efficiente di i ++. La loro chiave è prendere l'abitudine di usare il pre-incremento ogni volta che la semantica post-incremento non è effettivamente richiesta dal tuo algoritmo, e allora avrai l'abitudine di scrivere codice che per natura si presta a una maggiore efficienza, indipendentemente da come bene il tuo compilatore può ottimizzare.
fonagger

64

Sì. C'è.

L'operatore ++ può o non può essere definito come una funzione. Per i tipi primitivi (int, double, ...) gli operatori sono integrati, quindi il compilatore sarà probabilmente in grado di ottimizzare il tuo codice. Ma nel caso di un oggetto che definisce l'operatore ++ le cose sono diverse.

La funzione operator ++ (int) deve creare una copia. Questo perché postfix ++ dovrebbe restituire un valore diverso da quello che contiene: deve contenere il suo valore in una variabile temp, incrementare il suo valore e restituire la temp. Nel caso di operator ++ (), prefisso ++, non è necessario creare una copia: l'oggetto può incrementare se stesso e quindi semplicemente restituire se stesso.

Ecco un'illustrazione 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
}

Ogni volta che chiami l'operatore ++ (int) devi crearne una copia e il compilatore non può farci nulla. Quando viene data la scelta, utilizzare operator ++ (); in questo modo non si salva una copia. Potrebbe essere significativo nel caso di molti incrementi (anello grande?) E / o oggetti di grandi dimensioni.


2
"L'operatore di pre-incremento introduce una dipendenza dei dati nel codice: la CPU deve attendere il completamento dell'operazione di incremento prima che il suo valore possa essere utilizzato nell'espressione. Su una CPU con pipeline profonde, questo introduce uno stallo. Non esiste alcuna dipendenza da dati per l'operatore post incremento. " ( Game Engine Architecture (2a edizione) ) Quindi, se la copia di un incremento di post non è intensiva dal punto di vista computazionale, può comunque battere il pre-incremento.
Matthias,

Nel codice postfix, come funziona C t(*this); ++(*this); return t;Nella seconda riga, stai incrementando questo puntatore a destra, quindi come tsi aggiorna se lo stai incrementando. I valori di questo non erano già stati copiati t?
rasen58,

The operator++(int) function must create a copy.no non lo è. Non più copie dioperator++()
Severin Pappadeux il

47

Ecco un punto di riferimento per il caso in cui gli operatori di incremento si trovano in unità di traduzione diverse. Compilatore con g ++ 4.5.

Ignora i problemi di stile per ora

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

Test

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

risultati

Risultati (i tempi sono in secondi) con g ++ 4.5 su una macchina virtuale:

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

Test

Prendiamo ora il seguente file:

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

Non fa nulla nell'incremento. Questo simula il caso in cui l'incremento ha una complessità costante.

risultati

I risultati ora variano estremamente:

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

Conclusione

Performance-wise

Se non è necessario il valore precedente, prendere l'abitudine di utilizzare il pre-incremento. Siate coerenti anche con i tipi predefiniti, vi abituerete e non correrete il rischio di subire inutili perdite di prestazioni se sostituite un tipo incorporato con un tipo personalizzato.

Semantic-saggio

  • 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. Ancora una volta, ti abituerai, anche se non lo sei adesso.

Knuth.

L'ottimizzazione prematura è la radice di tutti i mali. Come la pessimizzazione prematura.


1
Test interessante. Ora, quasi due anni e mezzo dopo, gcc 4.9 e Clang 3.4 mostrano una tendenza simile. Clang è un po 'più veloce con entrambi, ma la disparità tra pre e postfix è peggiore di gcc.
masticare calzini

Quello che mi piacerebbe davvero vedere è un esempio del mondo reale in cui ++ i / i ++ fa la differenza. Ad esempio, fa differenza su uno degli iteratori standard?
Jakob Schou Jensen il

@JakobSchouJensen: Questi erano piuttosto intesi come esempi del mondo reale. Prendi in considerazione un'applicazione di grandi dimensioni, con strutture ad albero complesse (ad esempio kd-tree, quad-tree) o contenitori di grandi dimensioni utilizzati nei modelli di espressione (al fine di massimizzare il throughput dei dati sull'hardware SIMD). Se fa la differenza lì, non sono davvero sicuro del motivo per cui si dovrebbe ricorrere al post-incremento per casi specifici se non è necessario dal punto di vista semantico.
Sebastian Mach,

@phresnel: non credo che operator ++ sia nel tuo quotidiano un modello di espressione - ne hai un vero esempio? L'uso tipico di operator ++ è su numeri interi e iteratori. Ecco, penso che sarebbe interessante sapere se c'è qualche differenza (ovviamente non c'è differenza sugli interi, ma gli iteratori).
Jakob Schou Jensen,

@JakobSchouJensen: nessun esempio di business reale, ma alcune applicazioni di scricchiolio dei numeri in cui conti cose. Scritto iteratori, considera un ray tracciante che è scritto in idiomatico stile C ++, e hai un iteratore per l'attraversamento in profondità, in modo tale da for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }non preoccuparti dell'attuale struttura ad albero (BSP, kd, Quadtree, Octree Grid, ecc.). Tale un iteratore avrebbe bisogno di mantenere uno stato, ad esempio parent node, child node, indexe cose del genere. Tutto sommato, la mia posizione è, anche se esistono solo pochi esempi, ...
Sebastian Mach,

20

Non è del tutto corretto affermare che il compilatore non può ottimizzare la copia della variabile temporanea nel caso postfix. Un test rapido con VC mostra che, almeno, in alcuni casi può farlo.

Nel seguente esempio, il codice generato è identico per prefisso e postfisso, ad esempio:

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

Che tu faccia ++ testFoo o testFoo ++, otterrai comunque lo stesso codice risultante. In effetti, senza leggere il conteggio dell'utente, l'ottimizzatore ha portato il tutto a una costante. Così questo:

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

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

Ha provocato quanto segue:

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

Quindi, mentre è certamente il caso che la versione postfix potrebbe essere più lenta, potrebbe anche essere che l'ottimizzatore sia abbastanza buono da sbarazzarsi della copia temporanea se non la si utilizza.


8
Hai dimenticato di notare il punto importante che qui è tutto sommato. Se le definizioni degli operatori non sono disponibili, la copia eseguita nel codice fuori linea non può essere evitata; con inline l'ottimizzazione è abbastanza ovvio, quindi qualsiasi compilatore lo farà.
Blaisorblade,

14

La Guida allo stile di Google C ++ dice:

Preincrement e Predecrement

Utilizzare il modulo prefisso (++ i) degli operatori di incremento e decremento con iteratori e altri oggetti modello.

Definizione: quando una variabile viene incrementata (++ i o i ++) o decrementata (--i o i--) e il valore dell'espressione non viene utilizzato, è necessario decidere se preincrementare (decremento) o postincremento (decremento).

Pro: Quando il valore restituito viene ignorato, il modulo "pre" (++ i) non è mai meno efficiente del modulo "post" (i ++) ed è spesso più efficiente. Questo perché post-incremento (o decremento) richiede una copia di i, che è il valore dell'espressione. Se sono un iteratore o un altro tipo non scalare, la copia potrebbe essere costosa. Dato che i due tipi di incremento si comportano allo stesso modo quando il valore viene ignorato, perché non pre-incrementare sempre?

Contro: la tradizione sviluppata, in C, sull'uso del post-incremento quando non si usa il valore dell'espressione, specialmente per i loop. Alcuni trovano che post-incremento sia più facile da leggere, poiché il "soggetto" (i) precede il "verbo" (++), proprio come in inglese.

Decisione: per semplici valori scalari (non oggetto) non c'è motivo di preferire un modulo e ne consentiamo uno dei due. Per gli iteratori e altri tipi di modello, utilizzare il pre-incremento.


1
"Decisione: per semplici valori scalari (non oggetto) non c'è motivo di preferire un modulo e ne consentiamo uno. Per gli iteratori e altri tipi di template, utilizzare il pre-incremento."
Nosredna,

2
Eh, ... e cos'è quella cosa?
Sebastian Mach,

Il link menzionato nella risposta è attualmente rotto
karol

4

Vorrei sottolineare recentemente un eccellente post di Andrew Koenig su Code Talk.

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

Nella nostra azienda utilizziamo anche la convenzione di ++ iter per coerenza e prestazioni ove applicabile. Ma Andrew fa emergere dettagli trascurati riguardo a intenti e prestazioni. Ci sono momenti in cui vogliamo usare iter ++ anziché ++ iter.

Quindi, prima decidi il tuo intento e se pre o post non contano, vai con pre in quanto avrà qualche vantaggio in termini di prestazioni evitando la creazione di oggetti extra e lanciandoli.



4

@Ketan

... aumenta i dettagli trascurati per quanto riguarda l'intento contro le prestazioni. Ci sono momenti in cui vogliamo usare iter ++ anziché ++ iter.

Ovviamente post e pre-increment hanno una semantica diversa e sono sicuro che tutti concordano sul fatto che quando si utilizza il risultato è necessario utilizzare l'operatore appropriato. Penso che la domanda sia cosa si dovrebbe fare quando il risultato viene scartato (come nei forloop). La risposta a questa domanda (IMHO) è che, poiché le considerazioni sulle prestazioni sono al massimo trascurabili, dovresti fare ciò che è più naturale. Per me ++iè più naturale, ma la mia esperienza mi dice che sono in minoranza e l'utilizzo i++causerà meno sovraccarico di metallo per la maggior parte delle persone che leggono il tuo codice.

Dopo tutto questo è il motivo per cui la lingua non è chiamata " ++C". [*]

[*] Inserisci una discussione obbligatoria ++Csull'essere un nome più logico.


4
@Motti: (scherzando) Il nome C ++ è logico se ricordi che Bjarne Stroustrup C ++ inizialmente lo ha codificato come pre-compilatore generando un programma C. Quindi C ++ ha restituito un vecchio valore C. O potrebbe essere per migliorare il fatto che C ++ sia un po 'concettualmente difettoso fin dall'inizio.
Kriss

4
  1. ++ i - più veloce non usando il valore di ritorno
  2. i ++ - più veloce usando il valore di ritorno

Quando non si utilizza il valore restituito, si garantisce che il compilatore non utilizzi un valore temporaneo nel caso di ++ i . Non garantito per essere più veloce, ma garantito per non essere più lento.

Quando si utilizza il valore restituito i ++ consente al processore di inserire sia l'incremento che il lato sinistro nella pipeline poiché non dipendono l'uno dall'altro. ++ Potrei arrestare la pipeline perché il processore non può avviare il lato sinistro fino a quando l'operazione di pre-incremento non si è completamente interrotta. Ancora una volta, una stalla della pipeline non è garantita, dal momento che il processore può trovare altre cose utili su cui attaccare.


3

Mark: Volevo solo sottolineare che gli operatori ++ sono buoni candidati da inserire, e se il compilatore decide di farlo, la copia ridondante verrà eliminata nella maggior parte dei casi. (es. tipi di POD, che di solito sono iteratori.)

Detto questo, nella maggior parte dei casi è ancora meglio usare ++ iter. :-)


3

La differenza di prestazioni tra ++ie i++sarà più evidente se si considerano gli operatori come funzioni di ritorno del valore e come sono implementate. Per semplificare la comprensione di ciò che sta accadendo, i seguenti esempi di codice verranno utilizzati intcome se fossero a struct.

++iincrementa la variabile, quindi restituisce il risultato. Questo può essere fatto sul posto e con un tempo CPU minimo, richiedendo solo una riga di codice in molti casi:

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

Ma lo stesso non si può dire i++.

Post-incrementing, i++è spesso visto come la restituzione del valore originale prima dell'incremento. Tuttavia, una funzione può restituire un risultato solo al termine . Di conseguenza, diventa necessario creare una copia della variabile contenente il valore originale, incrementare la variabile, quindi restituire la copia con il valore originale:

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

Quando non vi è alcuna differenza funzionale tra pre-incremento e post-incremento, il compilatore può eseguire l'ottimizzazione in modo tale che non vi siano differenze di prestazioni tra i due. Tuttavia, se è coinvolto un tipo di dati composito come structo o class, il costruttore della copia verrà chiamato in post-incremento e non sarà possibile eseguire questa ottimizzazione se è necessaria una copia approfondita. Pertanto, il pre-incremento è generalmente più veloce e richiede meno memoria rispetto al post-incremento.


1

@Mark: ho cancellato la mia risposta precedente perché era un po 'capovolta, e meritavo un voto negativo solo per quello. In realtà penso che sia una buona domanda, nel senso che si chiede cosa c'è nella mente di molte persone.

La solita risposta è che ++ i è più veloce di i ++ e senza dubbio lo è, ma la domanda più grande è "quando dovrebbe interessarti?"

Se la frazione di tempo della CPU impiegata nell'incrementare gli iteratori è inferiore al 10%, potrebbe non interessarti.

Se la frazione di tempo della CPU impiegata nell'incrementare gli iteratori è maggiore del 10%, puoi vedere quali istruzioni stanno eseguendo tale iterazione. Verifica se puoi semplicemente incrementare numeri interi anziché utilizzare iteratori. È probabile che tu possa, e mentre in un certo senso potrebbe essere meno desiderabile, è molto probabile che risparmierai essenzialmente tutto il tempo trascorso in quegli iteratori.

Ho visto un esempio in cui l'incremento dell'iteratore consumava ben oltre il 90% delle volte. In tal caso, passare all'incremento di numeri interi ha ridotto il tempo di esecuzione essenzialmente di tale importo. (ovvero migliore della velocità 10x)


1

@wilhelmtell

Il compilatore può eliminare il temporaneo. Verbatim dall'altra discussione:

Il compilatore C ++ può eliminare i provvisori basati su stack anche se in tal modo si modifica il comportamento del programma. Collegamento MSDN per VC 8:

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


1
Questo non è rilevante. NRVO evita la necessità di copiare t in "CC :: operator ++ (int)" nel chiamante, ma i ++ copierà comunque il vecchio valore nello stack del chiamante. Senza NRVO, i ++ crea 2 copie, una da t e una indietro al chiamante.
Blaisorblade,

0

Un motivo per cui dovresti usare ++ i anche su tipi predefiniti in cui non ci sono vantaggi prestazionali è creare una buona abitudine per te stesso.


3
Scusa, ma questo mi preoccupa. Chi dice che è una "buona abitudine", quando non importa quasi mai? Se le persone vogliono renderlo parte della loro disciplina, va bene, ma distinguiamo ragioni significative da questioni di gusto personale.
Mike Dunlavey,

@MikeDunlavey ok, quindi quale parte usi normalmente quando non ha importanza? xD è l'uno o l'altro no? il post ++ (se lo usi con il significato generale. aggiorna, restituisci il vecchio) è completamente inferiore a ++ pre (aggiorna, ritorna) non c'è mai alcun motivo per cui vorresti avere meno prestazioni. nel caso in cui si desideri aggiornarlo dopo, il programmatore non farà nemmeno il post ++ allora. non perdere tempo a copiare quando lo abbiamo già. aggiornalo dopo averlo usato. poi i compilatori hanno il buon senso che tu volevi che avesse.
Pozza

@Puddle: Quando sento questo: "non c'è mai alcun motivo per cui vorresti avere meno prestazioni" So che sto ascoltando "penny saggio - libbra sciocco". Devi avere un apprezzamento delle magnitudini coinvolte. Solo se questo rappresenta oltre l'1% delle volte coinvolto, dovresti anche pensarci. Di solito, se stai pensando a questo, ci sono milioni di problemi più grandi che non stai prendendo in considerazione, e questo è ciò che rende il software molto più lento di quanto potrebbe essere.
Mike Dunlavey,

@MikeDunlavey ha rigettato le sciocchezze per soddisfare il tuo ego. stai cercando di sembrare un monaco saggio, ma non stai dicendo nulla. le magnitudini coinvolte ... se solo oltre l'1% delle volte dovresti preoccuparti ... xD dribbling assoluto. se è inefficiente, vale la pena conoscerlo e risolverlo. stiamo qui meditando su questo per quel motivo esatto! non siamo preoccupati di quanto possiamo guadagnare da questa conoscenza. e quando ho detto che non avresti voluto meno prestazioni, vai avanti, spiega uno scenario dannato allora. Signor saggio!
Pozza

0

Entrambi sono altrettanto veloci;) Se vuoi che sia lo stesso calcolo per il processore, è solo l'ordine in cui è fatto che differisce.

Ad esempio, il seguente codice:

#include <stdio.h>

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

Produrre il seguente assieme:

 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

Vedete che per a ++ e b ++ è un incl mnemonico, quindi è la stessa operazione;)


È C, mentre OP ha chiesto C ++. In C è lo stesso. In C ++ il più veloce è ++ i; a causa del suo oggetto. Tuttavia, alcuni compilatori possono ottimizzare l'operatore post-incremento.
Wiggler Jtag,

0

La domanda prevista riguardava quando il risultato non è stato utilizzato (questo è chiaro dalla domanda per C). Qualcuno può risolvere questo problema poiché la domanda è "wiki della community"?

A proposito di ottimizzazioni premature, Knuth viene spesso citato. Giusto. ma Donald Knuth non difenderebbe mai con quel codice orribile che puoi vedere in questi giorni. Hai mai visto a = b + c tra Java Integers (non int)? Ciò equivale a 3 conversioni boxe / unboxing. Evitare cose del genere è importante. E scrivere inutilmente i ++ anziché ++ i è lo stesso errore. EDIT: Come dice bene Phresnel in un commento, questo può essere riassunto come "l'ottimizzazione prematura è malvagia, così come la pessimizzazione prematura".

Anche il fatto che le persone siano più abituate a i ++ è una sfortunata eredità C, causata da un errore concettuale di K&R (se segui l'argomento intento, questa è una conclusione logica; e difendere K&R perché sono K&R non ha significato, sono fantastico, ma non sono grandi come designer del linguaggio; esistono innumerevoli errori nella progettazione C, che vanno da gets () a strcpy (), all'API strncpy () (avrebbe dovuto avere l'API strlcpy () dal primo giorno) ).

A proposito, sono uno di quelli che non sono abbastanza abituati a C ++ per trovare ++ che sono fastidioso da leggere. Tuttavia, lo uso poiché riconosco che è giusto.


Vedo che stai lavorando a un dottorato di ricerca. con interesse per l'ottimizzazione del compilatore e cose del genere. Che è grande, ma non dimenticare il mondo accademico è una cassa di risonanza, e il buon senso spesso viene lasciato fuori dalla porta, almeno in CS Si potrebbe essere interessato a questo: stackoverflow.com/questions/1303899/...
Mike Dunlavey

Non ho mai trovato ++ipiù fastidioso di i++(in effetti, l'ho trovato più interessante), ma il resto del tuo post ottiene il mio pieno riconoscimento. Forse aggiungere un punto "l'ottimizzazione prematura è male, così come la pessimizzazione prematura"
Sebastian Mach

strncpyserviva a uno scopo nei filesystem che stavano usando in quel momento; il nome file era un buffer di 8 caratteri e non doveva essere terminato con null. Non puoi biasimarli per non aver visto 40 anni nel futuro dell'evoluzione del linguaggio.
MM

@MattMcNabb: il nome file di 8 caratteri non era un'esclusiva MS-DOS? C è stato inventato con Unix. Ad ogni modo, anche se strncpy aveva ragione, la mancanza di strlcpy non era pienamente giustificata: anche gli originali C avevano array che non dovevi traboccare, che avevano bisogno di strlcpy; al massimo, mancavano solo agli aggressori intenzionati a sfruttare gli insetti. Ma non si può dire che prevedere questo problema fosse banale, quindi se riscrivo il mio post non userei lo stesso tono.
Blaisorblade,

@Blaisorblade: come ricordo, i primi nomi di file UNIX erano limitati a 14 caratteri. La mancanza di strlcpy()era giustificata dal fatto che non era ancora stata inventata.
Keith Thompson,

-1

È tempo di fornire alla gente gemme di saggezza;) - c'è un semplice trucco per fare in modo che l'incremento postfix C ++ si comporti più o meno come l'incremento prefisso (ho inventato questo per me stesso, ma l'ho visto anche nel codice di altre persone, quindi non lo sono solo).

Fondamentalmente, il trucco è usare la classe helper per posporre l'incremento dopo il ritorno, e RAII viene in soccorso

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

Inventato per un codice iteratore personalizzato pesante e riduce il tempo di esecuzione. Il costo del prefisso rispetto al postfisso ora è un riferimento e se questo è un operatore personalizzato che si sta muovendo pesantemente, il prefisso e il postfisso hanno prodotto lo stesso tempo di esecuzione per me.


-5

++iè più veloce di i++perché non restituisce una vecchia copia del valore.

È anche più intuitivo:

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

Questo esempio C stampa "02" anziché "12" che potresti aspettarti:

#include <stdio.h>

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

Lo stesso per C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.