基于我从编译器编写器收集的内容,值的类型在效率方面比引用/指针更受欢迎。
这是因为当您不必关心别名,外部更改的内存(指针指向的内存),指针取消引用的成本等等时,值类型更易于推理。我不得不说,虽然我理解这些问题但仍然有一些关于具体案例的问题。
案例#0
void foo(const float& f)
好的,我们在这里有一个参考,但它是不变的!当然我们有一个恒定的视图(ref),所以外部它可能会改变,但它只能在多线程世界中发生,我不确定如果没有使用同步原语,编译器是否必须考虑它。显然,如果在内部我们使用另一个指向浮点变量的指针/引用,我们可能会有修改f
参数的风险。编译器可以将此参数视为安全(假设我们不使用任何ref / ptr在内部浮动)?
案例#1
void foo(vector<int> f)
从C ++ 11/14程序员的角度来看,我知道vector
可以安全地移动到函数中。众所周知,容器内部有一个指向数组的指针。编译器是否会将指针视为安全(无需外部修改),因为我们只获得了向量的副本,因此我们是它的唯一所有者?
换句话说:是一个被视为安全对象的复制对象(因为逻辑上我们对对象进行了克隆),或者不允许编译器做出这样的假设和任何 ptr / ref会员必须被视为具有潜在危险性,因为副本ctor / op可能没有制作正确的副本。程序员在复制时不应该负责处理共享资源吗?
底线:
常量指针/引用和复制的复杂对象通常比基元的副本慢,因此应尽可能避免性能关键代码;或者他们的效率稍差,我们不应该担心它?
答案 0 :(得分:3)
作为现代C ++中的一般规则:
对于(廉价或复制)原始类型,如int
,float
或double
等,如果它是输入(只读)参数,只需按值传递:
inline double Square(double x)
{
return x*x;
}
相反,如果输入参数的类型不便于复制,但移动成本低,例如std::vector
,std::string
等,请考虑两个子案例:
一个。如果该函数只是观察该值,那么通过const
引用(这将防止无用的潜在昂贵的深拷贝):
double Average(const std::vector<double> & v)
{
.... // compute average of elements in v (which is read-only)
}
湾如果函数正在获取参数的本地副本,则按值传递,并从值传递std::move
(由于移动语义,这将允许优化) :
void Person::SetName(std::string name)
{
m_name = std::move(name);
}
答案 1 :(得分:2)
(作为评论开始但不适合。)
案例#0已被讨论过致死,例如:
Is it counter-productive to pass primitive types by reference?
这已经是另外两个问题的重复。特别是,我发现this answer对你的案例#0也是一个很好的答案。相关问题:
案例#1对我来说不清楚:您需要副本还是想要移动?两者之间存在巨大差异,你不清楚你所写的是哪一个。
如果参考文章足够,但你做了副本,那么就是在浪费资源。
如果你需要制作一份深层复印件,那就是它的全部内容,参考和移动都不会有帮助。
请阅读this answer并修改案例#1。
答案 2 :(得分:1)
案例#0
否 - 可能会进行外部修改:
void foo(const float& f) {
..use f..
..call a function which is not pure..
..use (and reload) f..
}
案例#1 ...编译器是否会将指针视为安全(无需外部修改),因为我们只获得了vector的副本,因此我们是它的唯一所有者?
否 - 一定是悲观的。它可以被教导依赖于一个实现,但总的来说,它没有合理的方法来跟踪指针通过所有可能的构造场景进行任意构造以验证它是否安全,即使实现是可见的。
底线:
分配和复制容器的成本往往远远大于装载和存储的成本 - 取决于您的程序,硬件和实现!
通过引用传递小对象和内置函数并不意味着优化器必须在实现可见时将其视为引用。例如。如果它看到调用者正在传递常量,则可以根据已知的常量值进行优化。相反,创建副本可能会影响优化程序的能力,因为复杂性会增加。关于是否通过值传递这种普通/小类型的烦恼是一种古老的微优化。相比之下,复制(非SSO)字符串或向量OTOH可能是巨大的。首先关注语义。
我编写了大量性能关键代码,并通过(适当的const限定)引用几乎所有内容 - 包括内置函数。您正在计算此时的指令和内存速度(对于您的参数),这在台式机和便携式计算机中非常低。在确定之前,我对台式机和笔记本电脑进行了大量测试。我这样做是为了统一 - 你不需要担心在嵌入式之外引入引用(存在开销的地方)的成本。同样,制作不必要的副本和任何必要的动态分配的成本往往要大得多。还要考虑对象具有额外的构造,复制和销毁功能来执行 - 即使看起来无辜的类型也可能比复制要花费更多。