Existe uma diferença de desempenho entre i ++ e ++ i em C ++?

352

Temos a pergunta : existe uma diferença de desempenho entre i++e ++i em C ?

Qual é a resposta para C ++?

Mark Harrison
fonte
Voltei a comentar, pois essas duas tags são a maneira mais fácil de encontrar perguntas dessa natureza. Eu também passei por outras pessoas que não tinham tags coesas e dei a elas tags coesas.
George Stocker
104
Existe uma diferença de desempenho entre o uso de C ++ e ++ C?
new123456
2
Artigo: É razoável usar o operador prefixo de incremento ++ it em vez do operador postfix it ++ para iteradores? # viva64.com/en/b/0093

Respostas:

426

[Resumo executivo: use ++ise você não tiver um motivo específico para usá-lo i++.]

Para C ++, a resposta é um pouco mais complicada.

Se ifor um tipo simples (não uma instância de uma classe C ++), a resposta dada para C ("Não há diferença de desempenho") é válida, pois o compilador está gerando o código.

No entanto, se ié uma instância de uma classe C ++, então i++e ++iestá fazendo chamadas para uma das operator++funções. Aqui está um par padrão dessas funções:

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 o compilador não está gerando código, mas apenas chamando uma operator++função, não há como otimizar a tmpvariável e seu construtor de cópias associado. Se o construtor de cópias for caro, isso poderá ter um impacto significativo no desempenho.

Mark Harrison
fonte
3
O que o compilador pode evitar é a segunda cópia para retornar tmp, alocando tmp no chamador, por meio do NRVO, conforme mencionado por outro comentário.
Blaisorblade 15/01/09
7
O compilador não pode evitar isso completamente se o operador ++ estiver embutido?
Eduard - Gabriel Munteanu
16
Sim, se o operador ++ estiver embutido e o tmp nunca for usado, ele poderá ser removido, a menos que o construtor ou destruidor do objeto tmp tenha efeitos colaterais.
Zan Lynx
5
@kriss: a diferença entre C e C ++ é que em C você tem uma garantia de que o operador será incorporado e, nesse ponto, um otimizador decente poderá remover a diferença; em vez disso, em C ++, você não pode assumir inlining - nem sempre.
Bluesorblade #
3
Eu marcaria com +1 se a resposta mencionasse algo sobre classes que mantêm ponteiros (automáticos, inteligentes ou primitivos) na memória alocada dinamicamente (heap), em que o construtor de cópias necessariamente executa cópias profundas. Nesses casos, não há argumento, ++ i é talvez uma ordem de magnitude mais eficiente que i ++. A chave é adquirir o hábito de usar o pré-incremento sempre que a semântica pós-incremento não for realmente exigida pelo seu algoritmo, e você terá o hábito de escrever código que, por natureza, se presta a uma maior eficiência, independentemente de como bem, seu compilador pode otimizar.
phonetagger 17/07/12
64

Sim. Há sim.

O operador ++ pode ou não ser definido como uma função. Para tipos primitivos (int, double, ...), os operadores são incorporados, portanto o compilador provavelmente poderá otimizar seu código. Mas no caso de um objeto que define o operador ++, as coisas são diferentes.

A função do operador ++ (int) deve criar uma cópia. Isso ocorre porque o postfix ++ deve retornar um valor diferente do que contém: ele deve reter seu valor em uma variável temp, incrementar seu valor e retornar a temp. No caso do operador ++ (), prefixo ++, não há necessidade de criar uma cópia: o objeto pode se incrementar e simplesmente retornar a si mesmo.

Aqui está uma ilustração do ponto:

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
}

Toda vez que você chama o operador ++ (int), deve criar uma cópia, e o compilador não pode fazer nada sobre isso. Quando for dada a opção, use operator ++ (); Dessa forma, você não salva uma cópia. Pode ser significativo no caso de muitos incrementos (loop grande?) E / ou objetos grandes.

wilhelmtell
fonte
2
"O operador de pré-incremento introduz uma dependência de dados no código: a CPU deve esperar que a operação de incremento seja concluída antes que seu valor possa ser usado na expressão. Em uma CPU com pipeline profundo, isso introduz uma paralisação. Não há dependência de dados para o operador pós-incremento ". ( Arquitetura do Game Engine (2ª edição) ) Portanto, se a cópia de um pós-incremento não for computacionalmente intensiva, ela ainda poderá superar o pré-incremento.
Matthias
No código do postfix, como isso funciona C t(*this); ++(*this); return t;Na segunda linha, você está incrementando o ponteiro this right, então como é tatualizado se você está incrementando isso. Os valores disso já não foram copiados t?
rasen58
The operator++(int) function must create a copy.não não é. Não há mais cópias do queoperator++()
Severin Pappadeux 07/04/19
47

Aqui está uma referência para o caso em que os operadores de incremento estão em diferentes unidades de tradução. Compilador com g ++ 4.5.

Ignore os problemas de estilo por enquanto

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

Incremento O (n)

Teste

// 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 (os tempos são em segundos) com o g ++ 4.5 em uma 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

Incremento de O (1)

Teste

Vamos agora pegar o seguinte arquivo:

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

Não faz nada no incremento. Isso simula o caso em que o incremento tem complexidade constante.

Resultados

Os resultados agora variam extremamente:

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

Conclusão

Em termos de desempenho

Se você não precisar do valor anterior, crie o hábito de usar o pré-incremento. Seja consistente mesmo com os tipos internos, você se acostumará e não corre o risco de sofrer uma perda desnecessária de desempenho se substituir um tipo interno por um personalizado.

Semântica

  • i++diz increment i, I am interested in the previous value, though.
  • ++idiz increment i, I am interested in the current valueou increment i, no interest in the previous value. Mais uma vez, você se acostumará, mesmo que não esteja no momento.

Knuth.

Otimização prematura é a raiz de todo o mal. Como é a pessimização prematura.

phresnel
fonte
11
Teste interessante. Agora, quase dois anos e meio depois, o gcc 4.9 e o Clang 3.4 mostram uma tendência semelhante. O clang é um pouco mais rápido com ambos, mas a disparidade entre pré e pós-fix é pior que o gcc.
mastigue meias
O que eu realmente gostaria de ver é um exemplo do mundo real onde ++ i / i ++ faz a diferença. Por exemplo, isso faz diferença em algum dos iteradores std?
Jakob Schou Jensen
@JakobSchouJensen: Estes foram destinados a serem exemplos do mundo real. Considere um aplicativo grande, com estruturas em árvore complexas (por exemplo, árvores kd, árvores quádruplas) ou grandes contêineres usados ​​em modelos de expressão (para maximizar a taxa de transferência de dados no hardware SIMD). Se isso faz diferença, não sei ao certo por que se recorrer ao pós-incremento para casos específicos, se isso não for necessário em termos semânticos.
Sebastian Mach
@ phresnel: Eu não acho que o operador ++ esteja no seu cotidiano como um modelo de expressão - você tem um exemplo real disso? O uso típico do operador ++ é em números inteiros e iteradores. Acho que seria interessante saber se há alguma diferença (não há diferença de números inteiros, é claro - mas iteradores).
Jakob Schou Jensen
@JakobSchouJensen: Nenhum exemplo de negócio real, mas alguns aplicativos de processamento de números nos quais você conta coisas. Iteradores Wrt, considere um traçador de raios que é escrito no estilo idiomático do C ++ e você possui um iterador para a travessia de profundidade, de modo que for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }, independentemente da estrutura da árvore real (BSP, kd, Quadtree, Octree Grid, etc.). Tal iterador seria necessário para manter um estado, por exemplo parent node, child node, indexe coisas assim. Ao todo, a minha posição é que, mesmo se houver apenas alguns exemplos, ...
Sebastian Mach
20

Não é totalmente correto dizer que o compilador não pode otimizar a cópia variável temporária no caso do postfix. Um teste rápido com VC mostra que, pelo menos, pode fazer isso em certos casos.

No exemplo a seguir, o código gerado é idêntico para prefixo e postfix, por exemplo:

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

Quer você faça ++ testFoo ou testFoo ++, ainda assim obterá o mesmo código resultante. De fato, sem ler a contagem do usuário, o otimizador reduziu a coisa toda a uma constante. Então, é isso:

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

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

Resultou no seguinte:

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

Portanto, embora certamente a versão do postfix possa ser mais lenta, pode ser que o otimizador seja bom o suficiente para se livrar da cópia temporária, se você não a estiver usando.

James Sutherland
fonte
8
Você esqueceu de observar o ponto importante de que aqui tudo está alinhado. Se as definições dos operadores não estiverem disponíveis, a cópia feita no código fora de linha não poderá ser evitada; com inline o optim é bastante óbvio, então qualquer compilador fará isso.
Blaisorblade 15/01/09
14

O Guia de Estilo do Google C ++ diz:

Pré-incremento e Pré-Predimento

Use o formato de prefixo (++ i) dos operadores de incremento e decremento com iteradores e outros objetos de modelo.

Definição: Quando uma variável é incrementada (++ i ou i ++) ou decrementada (--i ou i--) e o valor da expressão não é usado, é preciso decidir se deve aumentar ou diminuir (pós-incremento) ou pós-incremento (decremento).

Prós: quando o valor de retorno é ignorado, o formulário "pré" (++ i) nunca é menos eficiente que o formulário "post" (i ++) e geralmente é mais eficiente. Isso ocorre porque o pós-incremento (ou decremento) exige que seja feita uma cópia de i, que é o valor da expressão. Se eu for um iterador ou outro tipo não escalar, a cópia poderá ser cara. Como os dois tipos de incremento se comportam da mesma forma quando o valor é ignorado, por que não sempre pré-incrementar?

Contras: A tradição desenvolveu, em C, o uso de pós-incremento quando o valor da expressão não é usado, especialmente em loops. Alguns acham que o pós-incremento é mais fácil de ler, pois o "assunto" (i) precede o "verbo" (++), assim como em inglês.

Decisão: para valores escalares simples (não objetos), não há razão para preferir um formulário e nós permitimos isso. Para iteradores e outros tipos de modelo, use pré-incremento.

martjno
fonte
11
"Decisão: para valores escalares simples (não objetos), não há razão para preferir um formulário e nós permitimos qualquer um. Para iteradores e outros tipos de modelo, use pré-incremento."
Nosredna
2
Eh, e o que é isso?
Sebastian Mach
O link mencionado na resposta está quebrado no momento #
karol
4

Gostaria de destacar um excelente post de Andrew Koenig no Code Talk muito recentemente.

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

Em nossa empresa, também usamos a convenção do ++ iter para consistência e desempenho, quando aplicável. Mas Andrew levanta detalhes negligenciados em relação à intenção versus desempenho. Há momentos em que queremos usar o iter ++ em vez do ++ iter.

Portanto, primeiro decida sua intenção e, se pre ou post não interessar, vá com pre, pois ele terá algum benefício no desempenho, evitando a criação de um objeto extra e o jogando.


fonte
4

@Ketan

... levanta detalhes negligenciados em relação à intenção versus desempenho. Há momentos em que queremos usar o iter ++ em vez do ++ iter.

Obviamente, pós e pré-incremento têm semânticas diferentes e tenho certeza de que todos concordam que quando o resultado é usado, você deve usar o operador apropriado. Eu acho que a pergunta é o que devemos fazer quando o resultado é descartado (como em forloops). A resposta a esta pergunta (IMHO) é que, como as considerações de desempenho são insignificantes, você deve fazer o que é mais natural. Para mim ++ié mais natural, mas minha experiência me diz que sou uma minoria e o uso i++causará menos sobrecarga de metal para a maioria pessoas que lê seu código.

Afinal, é por isso que o idioma não é chamado " ++C". [*]

[*] Insira uma discussão obrigatória sobre ++Cser um nome mais lógico.

Motti
fonte
4
@ Motti: (brincando) O nome do C ++ é lógico se você se lembrar do Bjarne Stroustrup C ++ inicialmente o codificou como um pré-compilador que gera um programa em C. Portanto, o C ++ retornou um valor C antigo. Ou talvez seja para melhorar que o C ++ seja um pouco conceitualmente defeituoso desde o início.
kriss
4
  1. ++ i - mais rápido sem usar o valor de retorno
  2. i ++ - mais rápido usando o valor de retorno

Quando não estiver usando o valor de retorno, é garantido que o compilador não use um temporário no caso de ++ i . Não é garantido que seja mais rápido, mas que não será mais lento.

Ao usar o valor de retorno, o i ++ permite que o processador introduza o incremento e o lado esquerdo no pipeline, pois eles não dependem um do outro. ++ i pode parar o pipeline porque o processador não pode iniciar o lado esquerdo até que a operação de pré-incremento tenha se espalhado por todo o caminho. Novamente, não há garantia de uma paralisação de pipeline, pois o processador pode encontrar outras coisas úteis para se manter.

Hans Malherbe
fonte
3

Mark: Só queria ressaltar que os operadores ++ são bons candidatos a serem incorporados e, se o compilador optar por fazê-lo, a cópia redundante será eliminada na maioria dos casos. (por exemplo, tipos de POD, que geralmente são os iteradores.)

Dito isto, ainda é melhor usar o ++ iter na maioria dos casos. :-)

0124816
fonte
3

A diferença de desempenho entre ++ie i++será mais aparente quando você considerar os operadores como funções de retorno de valor e como elas são implementadas. Para facilitar a compreensão do que está acontecendo, os seguintes exemplos de código serão usados intcomo se fosse um struct.

++iincrementa a variável e retorna o resultado. Isso pode ser feito no local e com tempo mínimo de CPU, exigindo apenas uma linha de código em muitos casos:

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

Mas o mesmo não pode ser dito i++.

Pós-incremento,, i++geralmente é visto como retornando o valor original antes de incrementar. No entanto, uma função só pode retornar um resultado quando estiver concluída . Como resultado, torna-se necessário criar uma cópia da variável que contém o valor original, incrementar a variável e retornar a cópia mantendo o valor original:

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

Quando não há diferença funcional entre pré-incremento e pós-incremento, o compilador pode executar a otimização para que não haja diferença de desempenho entre os dois. No entanto, se um tipo de dados composto como a structou classestiver envolvido, o construtor de cópia será chamado no pós-incremento e não será possível executar essa otimização se uma cópia profunda for necessária. Como tal, o pré-incremento geralmente é mais rápido e requer menos memória que o pós-incremento.

DragonLord
fonte
1

@ Mark: eu apaguei minha resposta anterior, porque foi um pouco invertida, e merecia um voto negativo apenas por isso. Na verdade, acho que é uma boa pergunta, no sentido de perguntar o que está na cabeça de muitas pessoas.

A resposta usual é que ++ i é mais rápido que i ++, e sem dúvida é, mas a grande questão é "quando você deve se importar?"

Se a fração do tempo da CPU gasto no incremento de iteradores for menor que 10%, talvez você não se importe.

Se a fração do tempo de CPU gasto no incremento de iteradores for maior que 10%, você poderá verificar quais instruções estão fazendo essa iteração. Veja se você pode apenas incrementar números inteiros em vez de usar iteradores. As chances são de que você poderia, e embora possa ser, de certo modo, menos desejável, as chances são muito boas, você economizará essencialmente todo o tempo gasto nesses iteradores.

Eu vi um exemplo em que o incremento do iterador estava consumindo bem mais de 90% do tempo. Nesse caso, ir para o incremento inteiro reduziu o tempo de execução essencialmente por esse valor. (ou seja, melhor que 10x aceleração)

Mike Dunlavey
fonte
1

@wilhelmtell

O compilador pode excluir o temporário. Verbatim do outro segmento:

O compilador C ++ tem permissão para eliminar temporários baseados em pilha, mesmo que isso mude o comportamento do programa. Link MSDN para VC 8:

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

Mat Noguchi
fonte
11
Isso não é relevante. O NRVO evita a necessidade de copiar t em "CC :: operator ++ (int)" de volta para o responsável pela chamada, mas o i ++ ainda copiará o valor antigo na pilha do responsável pela chamada. Sem o NRVO, o i ++ cria 2 cópias, uma para te outra para o chamador.
Blaisorblade 15/01/09
0

Um dos motivos pelos quais você deve usar o ++ i, mesmo nos tipos internos em que não há vantagem de desempenho, é criar um bom hábito para si mesmo.

Josh
fonte
3
Desculpe, mas isso me incomoda. Quem disse que é um "bom hábito", quando quase nunca importa? Se as pessoas querem fazer parte de sua disciplina, tudo bem, mas vamos distinguir razões significativas de questões de gosto pessoal.
Mike Dunlavey 24/08/09
@ MikeDunlavey ok, então de que lado você costuma usar quando isso não importa? xD é um ou o outro não é! o post ++ (se você o estiver usando com o significado geral. atualize-o, retorne o antigo) é completamente inferior ao ++ pré (atualize-o, retorne); nunca há qualquer motivo pelo qual você queira ter menos desempenho. no caso em que você deseja atualizá-lo depois, o programador nem fará o post ++. sem perder tempo copiando quando já o temos. atualize-o depois de usá-lo. então os compiladores têm o bom senso que você deseja que ele tenha.
Puddle
@ Poça: Quando ouço isso: "nunca há razão para você querer ter menos desempenho" Eu sei que estou ouvindo "um centavo sábio - um tolo". Você precisa ter uma apreciação das magnitudes envolvidas. Somente se isso for responsável por mais de 1% do tempo envolvido, você deve pensar um pouco. Normalmente, se você está pensando sobre isso, existem problemas milhões de vezes maiores que você não está considerando, e é isso que torna o software muito mais lento do que poderia ser.
Mike Dunlavey
@ MikeDunlavey regurgitou bobagens para satisfazer seu ego. você está tentando parecer um monge sábio, mas não está dizendo nada. as magnitudes envolvidas ... se apenas mais de 1% do tempo você deveria se importar ... xD drible absoluto. se for ineficiente, vale a pena conhecer e consertar. estamos aqui pensando nisso exatamente por isso! não estamos preocupados com o quanto podemos ganhar com esse conhecimento. e quando eu disse que você não iria querer menos desempenho, vá em frente, explique um cenário maldito. MR WISE!
Puddle
0

Ambos são tão rápidos;) Se você deseja que seja o mesmo cálculo para o processador, é apenas a ordem na qual isso é feito.

Por exemplo, o seguinte código:

#include <stdio.h>

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

Produza o seguinte conjunto:

 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

Você vê que, para a ++ e b ++, é um mnemônico incl. Portanto, é a mesma operação;)

Geoffroy
fonte
É C, enquanto o OP perguntou ao C ++. Em C é o mesmo. Em C ++, o mais rápido é ++ i; devido ao seu objeto. No entanto, alguns compiladores podem otimizar o operador pós-incremento.
Wiggler Jtag
0

A pergunta pretendida era sobre quando o resultado não é utilizado (isso fica claro na pergunta para C). Alguém pode consertar isso, pois a pergunta é "wiki da comunidade"?

Sobre otimizações prematuras, Knuth é frequentemente citado. Está certo. mas Donald Knuth nunca defenderia com esse código horrível que você pode ver nestes dias. Já viu a = b + c entre inteiros Java (não int)? Isso equivale a três conversões de boxe / unboxing. Evitar coisas assim é importante. E escrever inutilmente i ++ em vez de ++ i é o mesmo erro. EDIT: Como o phresnel coloca bem em um comentário, isso pode ser resumido como "otimização prematura é má, assim como pessimização prematura".

Mesmo o fato de as pessoas estarem mais acostumadas ao i ++ é um infeliz legado de C, causado por um erro conceitual da K&R (se você seguir o argumento da intenção, essa é uma conclusão lógica; defender a K&R porque é K&R não tem sentido, eles são ótimo, mas eles não são ótimos como designers de linguagem; existem inúmeros erros no design C, variando de gets () a strcpy (), até a API strncpy () (ela deveria ter a API strlcpy () desde o primeiro dia) )

Btw, eu sou um daqueles que não são usados ​​o suficiente para C ++ para encontrar ++ eu chato de ler. Ainda assim, eu uso isso porque reconheço que está certo.

Blaisorblade
fonte
Vejo que você está trabalhando em um doutorado. com interesse na otimização do compilador e coisas desse tipo. Isso é ótimo, mas não esqueça que a academia é uma câmara de eco, e o senso comum geralmente é deixado do lado de fora da porta, pelo menos no CS Você pode estar interessado nisso: stackoverflow.com/questions/1303899/…
Mike Dunlavey
Eu nunca achei ++imais chato do que i++(na verdade, achei mais legal), mas o resto do seu post recebe meu total reconhecimento. Talvez adicionar um ponto de "otimização prematura é mau, como é pessimization prematuro"
Sebastian Mach
strncpyserviu a um propósito nos sistemas de arquivos que eles estavam usando na época; o nome do arquivo era um buffer de 8 caracteres e não precisava ser terminado por nulo. Você não pode culpá-los por não verem 40 anos no futuro da evolução da linguagem.
MM
@MattMcNabb: o nome do arquivo com 8 caracteres não era exclusivo do MS-DOS? C foi inventado com o Unix. De qualquer forma, mesmo que o strncpy tivesse razão, a falta de strlcpy não era totalmente justificada: até o C original tinha matrizes que você não deveria transbordar, o que precisava de strlcpy; no máximo, faltavam apenas atacantes com a intenção de explorar os bugs. Mas não se pode dizer que a previsão desse problema foi trivial; portanto, se eu reescrevesse minha postagem, não usaria o mesmo tom.
Blaisorblade
@Blaisorblade: Pelo que me lembro, os primeiros nomes de arquivos UNIX eram limitados a 14 caracteres. A falta de strlcpy()foi justificada pelo fato de ainda não ter sido inventada.
Keith Thompson
-1

Hora de fornecer às pessoas gemas de sabedoria;) - existe um truque simples para fazer com que o incremento do postfix do C ++ se comporte da mesma forma que o incremento de prefixo (inventado por mim mesmo, mas o vi também no código de outras pessoas, por isso não estou sozinho).

Basicamente, o truque é usar a classe auxiliar para adiar o incremento após o retorno, e o RAII vem para resgatar

#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 é para alguns códigos pesados ​​de iteradores personalizados e reduz o tempo de execução. O custo do prefixo versus o postfix é uma referência agora e, se esse é um operador personalizado que faz movimentos pesados, o prefixo e o postfix produziram o mesmo tempo de execução para mim.

Severin Pappadeux
fonte
-5

++ié mais rápido do que i++porque não retorna uma cópia antiga do valor.

Também é mais intuitivo:

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

Este exemplo C imprime "02" em vez dos "12" que você pode esperar:

#include <stdio.h>

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

O mesmo para C ++ :

#include <iostream>
using namespace std;

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