C++ i++;和++;我在C++;?

C++ i++;和++;我在C++;?,c++,performance,oop,post-increment,pre-increment,C++,Performance,Oop,Post Increment,Pre Increment,我们有一个问题 < C++ > 的答案是什么?[执行摘要:如果没有具体的原因使用 I++< /COD> 对于C++,答案是有点复杂。 < > > 是一个简单类型(不是C++类的实例),因为编译器生成代码,所以保持/< 但是,如果 i 是C++类的一个实例,那么 i++> /COD>和 ++i 正在调用一个函数。以下是这些函数的标准对: Foo& Foo::operator++() // called for ++i { this->data += 1; re

我们有一个问题


< C++ >

的答案是什么?[执行摘要:如果没有具体的原因使用<代码> I++< /COD> 对于C++,答案是有点复杂。

< > <代码> > 是一个简单类型(不是C++类的实例),因为编译器生成代码,所以保持/< <>但是,如果<代码> i <代码>是C++类的一个实例,那么<代码> i++> /COD>和 ++i 正在调用一个函数。以下是这些函数的标准对:

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;
}
由于编译器不生成代码,而只是调用
运算符+++
函数,因此无法优化
tmp
变量及其关联的复制构造函数。如果复制构造函数很昂贵,那么这可能会对性能产生重大影响。

是。有

++运算符可以定义为函数,也可以不定义为函数。对于基本类型(int,double,…),操作符是内置的,因此编译器可能能够优化代码。但是对于定义++运算符的对象,情况就不同了

运算符++(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
}

每次调用运算符++(int)时,都必须创建一个副本,而编译器对此无能为力。选择时,使用运算符++();这样您就不会保存副本。在许多增量(大循环?)和/或大对象的情况下,这可能很重要。

说编译器不能在后缀情况下优化掉临时变量副本并不完全正确。用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());
}

因此,虽然后缀版本的速度肯定会慢一些,如果您不使用临时副本,那么优化器很可能会很好地消除它。

即使在没有性能优势的内置类型上,您也应该使用++i的原因是为自己创造一个好习惯。

@wilhelmtell

编译器可以省略临时代码。来自另一个线程的逐字记录:

允许C++编译器消除基于栈的临时性,即使这样做改变了程序行为。VC 8的MSDN链接:

报纸上说:

预增量和预减量

将递增运算符和递减运算符的前缀形式(++i)与 迭代器和其他模板对象

定义:当变量递增(++i或i++)或递减(--i或 我--)并且表达式的值没有被使用,我们必须决定 是预递增(递减)还是后递增(递减)

优点:当忽略返回值时,“pre”形式(++i)永远不会减少 比“post”表单(i++)更高效,并且通常更高效。 这是因为后期增量(或减量)需要一个i到的副本 ,这是表达式的值。如果我是迭代器 其他非标量类型,复制i可能会很昂贵。自从两人 当忽略该值时,增量类型的行为相同,为什么不呢 总是预先增加

缺点:在C语言中,当 未使用表达式值,尤其是在for循环中。一些发现 后增量更易于阅读,因为“主题”(i)位于 “动词”(++),就像英语一样

决策:对于简单标量(非对象)值,没有理由选择一个 形式,我们允许任何一种。对于迭代器和其他模板类型,使用 增量前


我想指出Andrew Koenig最近在Code Talk上发表的一篇精彩文章

在我们公司,在适用的情况下,我们还使用++国际热核聚变实验堆的一致性和性能惯例。但Andrew提出了关于意图与性能的过度关注的细节。有时我们希望使用iter++而不是iter++

因此,首先确定您的意图,如果pre或post不重要,那么使用pre,因为它将通过避免创建额外对象并将其抛出而获得一些性能优势。

@Ketan

…提出了有关意图与性能的详细信息。有时我们希望使用iter++而不是iter++

显然,post和pre-increment具有不同的语义,我相信每个人都同意,当使用结果时,应该使用适当的运算符。我认为问题是当结果被丢弃时(如
for
循环)应该怎么做。这个问题(IMHO)的答案是,由于性能考虑最多可以忽略不计,所以您应该做更自然的事情。对于我自己来说,
++i
更自然,但我的经验告诉我,我是少数人,使用
i++
会减少大多数人阅读代码的金属开销

毕竟,这就是为什么这种语言没有被称为“
++C
”[*]


[*]插入关于
++C
是一个更符合逻辑的名称的强制性讨论

Mark:我只是想指出,操作符+++是内联的很好的候选对象,如果编译器选择这样做,在大多数情况下,冗余副本将被消除。(例如,迭代器通常是POD类型。)


也就是说,在大多数情况下使用++iter仍然是更好的风格。:-)

预期的问题是关于结果何时未使用(这从
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)] 
#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.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;
}
// 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;
}
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
// 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
int& int::operator++() { 
     return *this += 1;
}
int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}
x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 
#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}
#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
#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;
}