宁愿偏爱提前含量而不是插入后更好吗?

Is it still better to prefer pre-increment over post-increment?

本文关键字:插入 更好 偏爱      更新时间:2023-10-16

曾经是首选提前的情况,因为上课后的插入后插入后,需要返回临时副本,该临时副本在增量之前表示对象的状态。

看来,这不再是一个严重的问题(只要在内置到位),因为我的旧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;
}

两个功能的结果组件是:

    .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

如果没有任何划分的内容,则似乎仍然有好处,因为test_post被迫召集到operator++(int)

让我们假设operator++(int)是惯用的复制构造函数,呼叫预先提示并返回副本,如上所述。如果复制构造函数是构成夹具或默认复制构造函数实现,那么编译器的足够信息可以优化加入后,以使test_pretest_post成为相同的功能?如果不是,则还需要哪些信息?

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

但是,对于类型,它(如果不是 dim )很重要,因为在这种情况下,语义可能会更加复杂。

class X { /* code */ };
X x;
++x;
x++; 

最后一个两个调用可能完全不同,并且可能执行不同的事情,就像这些调用一样:

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

所以不要让自己被困在句法糖中。

通常是用户定义的类型中的increment operator,涉及创建副本,比典型的投入前操作员更昂贵。

因此,应将预入口操作员用于偏爱用户定义的类型。

也是一致的良好风格,因此在内置类型中也应优先考虑预先提前。

示例:

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

不能期望编译器优化用户定义类型的插入后,因为其实现是惯例,而不是编译器可以推断出来的东西。

除了可能更有效的效率之外,您应该(通常)(通常)更喜欢提取前的主要原因,而不是提交后,是前者是您真正的真正原因首先是指

当您写一个循环标头时

for ( std::size_t i = 0; i < numElements; i++ )

您并不是说"请在i的价值中添加一个,然后给我旧价值"。您根本不在乎Expression i 的返回值!那么,为什么要让编译器跳过箍,并给出一个返回值,该值最多需要工作呢?

我意识到编译器通常会优化不必要的额外工作,但是为什么不只是说您的意思,而不是希望编译器弄清楚您的意思是什么?

优化编译器会做各种奇妙而神奇的事情,尤其是当您不运行调试构建时,但没有进入内部详细信息,就应用了用户定义的类型的预入前操作员是在不再努力写作或维护的同时,仍然会像快速或更快。

,就像您可以习惯于编写a>b ? a:b之类的代码,以代替使用Max函数,并且在这种情况下,优化编译器通常会发出无分支代码。但是,当我们可以更轻松而可以说更清晰时,它可以发挥什么目的,写max(a, b)

当您可以实现更快或更快的速度而没有额外的努力或成本的速度或成本的速度,而不是在最坏的情况下,旧风格习惯的略有变化,那时我认为我们应该不再寻求优化器寻求答案。优化器应该在那里制造最初付出更多努力并更便宜的维护费用。

更便宜。

我选择了纳瓦兹的答案作为最好的。我通常同意大多数评论和其他答案,即提前提前仍然应该优先考虑。但是,我想了解编译器如何能够确定一个可以与另一个相同的语义对待。可以肯定的是,人们可能会说:"没关系,您不应该使用后报道。"但是,这个答案并不能真正满足我的智力好奇心。


如果复制构造函数和破坏者(意味着它包含的任何对象也将具有微不足道的破坏者),则编译器似乎具有足够的信息来对待类,就像内置类型一样,既是琐碎的)是惯用的。

单独的惯用性嵌入式插入后和琐碎的复制构造函数不足以使编译器推断出可以同样实现这两个函数test_pretest_post。如果驱动器是非平凡的,则代码有所不同。即使有一个空的破坏者主体,插入后组件对于所讨论的编译器略有变化,GCC 4.4.7:

        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        .cfi_lsda 0x3,.LLSDA1106
        pushq   %rbx
        .cfi_def_cfa_offset 16
        .cfi_offset 3, -16
        movq    %rdi, %rbx
.LEHB0:
        call    _ZN3IntppEv
.LEHE0:
        movq    %rbx, %rax
        popq    %rbx
        .cfi_remember_state
        .cfi_def_cfa_offset 8
        ret
.L12:
        .cfi_restore_state
.L9:
        movq    %rax, %rdi
.LEHB1:
        call    _Unwind_Resume
.LEHE1:
        .cfi_endproc

观察到执行路径在很大程度上是相同的,除了一些额外的.cfi_*语句,这些语句在预告仪版本中没有出现,以及对_Unwind_Resume的未访问呼叫。我相信添加了额外的代码来处理案例,灾难剂会引发例外。删除死亡代码清除了其中的一些,因为驱动器主体是空的,但结果与提前版本并不相同。