更喜欢预增量超过后增量更好吗?

jxh*_*jxh 11 c++ optimization

以前的情况是预增量是首选的,因为类上的重载后增量需要返回表示增量前对象状态的临时副本.

似乎这不再是一个严重的问题(只要内联就位),因为我的旧C++编译器(GCC 4.4.7)似乎将以下两个函数优化为相同的代码:

class Int {
    //...
public:
    Int (int x = 0);
    Int & operator ++ ();
    Int operator ++ (int) {
        Int x(*this);
        ++*this;
        return x;
    }
};

Int & test_pre (Int &a) {
    ++a;
    return a;
}

Int & test_post (Int &a) {
    a++;
    return a;
}
Run Code Online (Sandbox Code Playgroud)

两个函数的结果汇编是:

    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    pushq   %rbx
    .cfi_def_cfa_offset 16
    .cfi_offset 3, -16
    movq    %rdi, %rbx
    call    _ZN3IntppEv
    movq    %rbx, %rax
    popq    %rbx
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

然而,如果没有任何内联,那么优先选择预增量到后增量似乎仍然有好处,因为它test_post被强制调用operator++(int).

我们假设operator++(int)内联为惯用的复制构造函数,调用预增量,并返回副本,如上所示.如果拷贝构造函数被内联或默认拷贝构造函数的实现,是有足够的信息,编译器优化后递增,这样test_pretest_post成为相同的功能?如果没有,还需要哪些其他信息?

Naw*_*waz 16

是.内置类型无关紧要.对于这样的类型,编译器可以轻松地分析语义并优化它们 - 如果这不会改变行为.

然而,对于类类型,它可能(如果不是)的事情,因为语义可能是在这种情况下更复杂.

class X { /* code */ };

X x;

++x;
x++; 
Run Code Online (Sandbox Code Playgroud)

最后两个调用可能完全不同,可能会执行不同的操作,就像这些调用一样:

x.decrement(); //may be same as ++x (cheating is legal in C++ world!)
x.increment(); //may be same as x++
Run Code Online (Sandbox Code Playgroud)

所以不要让自己被困在语法糖中.

  • @jxh:你的编辑不会增加太多.关键是,编译器*可以自由地将你的`i ++`改为`++ i`****如果**它可以证明这种转换不会改变*程序的行为*.对于内置类型,样张很容易.对于班级类型,它可能*真的*很难(或*实际上*昂贵)! (2认同)

Gal*_*lik 9

通常,用户定义类型中的后增量运算符涉及创建比典型的预增量运算符更慢且更昂贵的副本.

因此,应优先使用预增量运算符,而不是用户定义的类型.

同样是一致的好风格,因此内置类型也应该首选预增量.

例:

struct test
{
    // faster pre-increment
    test& operator++() // pre-increment
    {
        // update internal state
        return *this; // return this
    }

    // slower post-increment
    test operator++(int)
    {
        test c = (*this); // make a copy
        ++(*this); // pre-increment this object
        return c; // return the un-incremented copy
    }
};
Run Code Online (Sandbox Code Playgroud)

不能期望编译器优化用户定义类型的后递增,因为它们的实现是约定,而不是编译器可以推断的.