C++参数类型和效率

4
据我从编译器作者那里了解到的信息,就效率而言,值类型比引用/指针更受欢迎。
这是因为当您无需关心别名、外部更改的内存(指针所指的内存)、指针解引用的成本等问题时,值类型更容易理解。尽管我理解这些问题,但仍对特定情况有几个问题。
案例#0:
void foo(const float& f)
好的,我们这里有一个引用,但它是常量!确实,我们有一个常量视图(ref),因此在外部可能会更改,但这只会发生在多线程世界中,如果没有使用同步原语,则我不确定编译器是否必须考虑它。显然,如果我们在内部使用另一个指向任何浮点变量的指针/引用,我们可能会冒着修改f参数的风险。编译器可以将此参数视为安全的吗(假设我们不在内部使用任何float的引用/指针)?
案例#1:
void foo(vector f)
从C++11/14程序员的角度来看,我知道vector可以安全地移动到函数中。正如我们所知,容器在内部保存指向数组的指针。编译器能将指针视为安全(无外部修改),因为我们只是得到了vector的副本,因此我们是唯一的所有者吗?
换句话说:复制的对象是否被视为安全(因为逻辑上我们克隆了对象),或者编译器不允许做出这样的假设,任何指针/引用成员都必须视为可能危险,因为复制构造函数/运算符可能没有正确地复制。在复制它们时,程序员不应该负责处理共享资源吗?
底线:
常量指针/引用和复制的复杂对象通常比基本类型的副本慢,因此在性能关键的代码中应尽可能避免使用它们;还是它们效率略低,我们不应过于担心?

2
我对这个问题不太确定...“常量指针和复制的对象是否比原始类型的副本慢”?这些都是解决不同问题的不同事物。你到底想要比较什么,需要解决哪个问题? - Kerrek SB
1
投票关闭,过早的优化=>不是特定案例=>太宽泛。 - MSalters
3个回答

3

在现代C++中,有一些通用的规则:

  1. For (cheap to copy) primitive types, like int, float or double, etc., if it's an input (read-only) parameter, just pass by value:

    inline double Square(double x)
    {
        return x*x;
    }
    
  2. Instead, if the type of the input parameter is not cheap to copy, but it's cheap to move, e.g. std::vector, std::string, etc., consider two sub-cases:

    a. If the function is just observing the value, then pass by const reference (this will prevent useless potentially expensive deep-copies):

    double Average(const std::vector<double> & v)
    {
        .... // compute average of elements in v (which is read-only)
    }
    

    b. If the function is taking a local copy of the parameter, then pass by value, and std::move from the value (this will allow optimizations thanks to move semantics):

    void Person::SetName(std::string name)
    {
        m_name = std::move(name);
    }
    

2
哦,这个例子不好。你应该使用 v 而不是将它移动到 w 中。 - user541686
这个问题并不是在询问传递参数的效率,我认为 OP 已经默认了这一点,而是在使用它们方面。更具体地说,是关于编译器执行优化的机会。 - Gorpik
@Mr.C64:好的,说得也是。 :-S - Kerrek SB
@jork:然后它再次在m_name中被“移动”,而不是进行(无用的)深拷贝。 - Mr.C64
啊,这是一个 setter。现在我明白了,抱歉 :) - jrok
显示剩余4条评论

2

(作为注释开始,但它不适合。)

案例#0已经被讨论得死去活来了,例如:

将基本类型按引用传递是否具有反效果?

这已经是其他两个问题的重复。特别地,我认为这个答案也是您的案例#0的一个好答案。相关问题:

案例#1对我不清楚:您需要副本还是要移动?两者之间有巨大的差异,从您的写作中不清楚您需要哪一个。

如果引用足够但您却进行了复制,则会浪费资源。

如果您需要进行深层复制,则只能这样做,引用和移动都无法帮助。

请阅读这个答案并修订案例#1。


1
案例 #0
不 - 它可能会被外部修改:
void foo(const float& f) {
 ..use f..
 ..call a function which is not pure..
 ..use (and reload) f..
}

案例 #1 ... 编译器会将指针视为安全的(无外部修改),因为我们刚刚得到了 vector 的一个副本,所以我们是唯一的所有者吗?

不会 - 它必须保持悲观。它可以被教导依靠实现,但一般来说,它没有合理的方式来跟踪那个指针通过所有可能的构造场景来验证它是否安全,即使实现是可见的。

总之:

分配和复制容器的成本往往比负载和存储的成本要大得多 - 这取决于您的程序、硬件和实现!

通过引用传递小对象和内置类型并不意味着优化器在实现可见时必须将其视为引用。例如,如果它看到调用者正在传递一个常量,它有权根据已知的常量值进行优化。相反,创建一个副本可能会干扰优化程序的能力,因为复杂性可能会增加。担心是否通过值传递此微不足道/小型类型是一种旧的微优化。然而,复制一个(非 SSO)字符串或向量则可能相对巨大。首先关注语义。

我编写了大量的性能关键代码,并通过(适当地使用const限定符)引用传递几乎所有内容 - 包括内置类型。此时,您正在计算指令和内存速度(对于参数),这在桌面和便携式计算机中非常低。在选择该方法之前,我在台式机和笔记本电脑上进行了大量测试。我这样做是为了统一性 - 您不需要担心引入引用的成本(其中存在开销)在嵌入式系统之外。再次强调,制作不必要的副本以及任何必要的动态分配的成本往往要高得多。还要考虑到对象具有额外的构造、复制和销毁函数来执行 - 即使是看起来无害的类型,复制的成本也可能比引用更高。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接