std::vector的有效传递 当C++函数接受一个代码> STD::vector < /Calp>参数时,通常的模式是通过 const 参考,如: int sum2(const std::vector<int> &v) { int s = 0; for(size_t i = 0; i < v.size(); i++) s += fn(v[i]); return s; }

std::vector的有效传递 当C++函数接受一个代码> STD::vector < /Calp>参数时,通常的模式是通过 const 参考,如: int sum2(const std::vector<int> &v) { int s = 0; for(size_t i = 0; i < v.size(); i++) s += fn(v[i]); return s; },c++,stl,vector,arguments,C++,Stl,Vector,Arguments,通过传递一个随机访问迭代器对,可以获得类似的性能,但方便性要小得多。我的问题是:这个想法有什么缺陷?我认为聪明人应该有充分的理由接受支付向量引用的性能成本,或者处理迭代器带来的不便 编辑:基于下面的内容,如果我只把建议的 VECURTRORIF 类重命名为切片或范围,请考虑情况。其目的是使用具有更自然语法的随机访问迭代器对。按值传递,除非您确定按引用传递可以提高性能 当您传递值时,可能会发生复制省略,这将导致类似的性能(如果不是更好的话) 戴夫在这里写到: 我相信这段代码在访问向量元素时会导致

通过传递一个随机访问迭代器对,可以获得类似的性能,但方便性要小得多。我的问题是:这个想法有什么缺陷?我认为聪明人应该有充分的理由接受支付向量引用的性能成本,或者处理迭代器带来的不便


编辑:基于下面的内容,如果我只把建议的<代码> VECURTRORIF <代码>类重命名为切片或范围,请考虑情况。其目的是使用具有更自然语法的随机访问迭代器对。

按值传递,除非您确定按引用传递可以提高性能

当您传递值时,可能会发生复制省略,这将导致类似的性能(如果不是更好的话)

戴夫在这里写到:

我相信这段代码在访问向量元素时会导致双重解引用

不一定。编译器非常聪明,应该能够看到操作符
[]
不会更改“指向第一个元素的指针”,因此无需让CPU在每次循环迭代时从内存中重新加载它

这个想法有什么缺陷


简单:这是过早的优化。替代方法:接受向量常量&,并使用迭代器或将迭代器直接传递给函数。

没有双解引用,因为编译器可能会将指向向量的实指针作为参数传递,而不是指向指针的指针。您只需尝试一下,然后查看IDE的反汇编视图,了解幕后的实际情况:

void Method(std::vector<int> const& vec) {
 int i = vec.back();
}


void SomeOtherMethod() {
  std::vector<int> vec;
  vec.push_back(1);
  Method(vec);
}
毫不奇怪,指向向量的指针被传递,因为引用只是永久性的取消引用的指针。现在在Method()中,再次访问向量:

mov         ecx,dword ptr [ebp+8] 
call        std::vector<int,std::allocator<int> >::back (8276100h)
mov ecx,dword ptr[ebp+8]
调用std::vector::back(8276100h)

向量指针直接从堆栈中获取并写入ecx

你是对的,这里有一个额外的间接性。如果编译器(在链接时代码生成的帮助下)对其进行优化,这是可以想象的(尽管这会令人惊讶)

您提出的方法有时称为切片,在某些情况下被广泛使用。不过,总的来说,我不确定这是否值得冒险。你必须非常小心使你的切片(或其他人的切片)无效

请注意,如果对循环使用迭代器而不是索引,则只需对引用进行几次解引用(调用
begin()
end()
),而不是n次(索引到向量中)

int和(常数向量&v)
{
int s=0;
对于(自动it=v.begin();it!=v.end();+it){
s+=fn(*it);
}
返回s;
}
(我假设优化器将把
end()
调用从循环中提升出来。您可以显式地这样做来确定。)


传递一对迭代器而不是容器本身似乎是STL习惯用法。这将使您具有更多的通用性,因为容器的类型可能会有所不同,但所需的解引用数量也会有所不同。

您的想法的错误之处在于,您已经有了两个非常好的解决方案:

  • 按原样传递向量,或者按值传递(编译器通常会消除副本),或者按(const)引用传递,并信任编译器消除双重间接寻址,或者
  • 传递一个迭代器对
当然,你可以说迭代器对是“不太自然的语法”,但我不同意。这对于任何习惯STL的人来说都是非常自然的。它是高效的,并且使用std算法或您自己的函数,精确地为您提供处理范围所需的内容

迭代器对是一个常见的C++习惯用法,C++程序员读代码会理解它们而不会有问题,而他们会对你的家用矢量包装器感到惊讶。


如果您真的对性能有疑虑,请传递这对迭代器。如果语法真的困扰您,请传递向量并信任编译器。

您是否确实比较了这两种情况下生成的机器代码?或者测量了性能?您是否检查过装配时是否确实如此?我也不认为迭代器对的约定是出于性能考虑,而是设计决策(事实上,boost支持迭代器范围对象这一更方便的概念,尽管我没有研究它们到底是如何工作的)。我认为你的评论实际上没有任何意义。引用是对象的别名。因此,访问对象的引用不会比访问原始对象更昂贵。这篇有点煽动性的诽谤是你的猜测,如果你做了两分钟的尽职调查,你就不需要这篇文章了。@Martin:引用是一个带有语法糖的指针。说真的。引用是一个无法重置的指针(指向另一个对象)。由于此属性,编译器可能能够更有效地优化使用引用而不是指针的代码。但是引用确实与对象本身大不相同。当代码比原始版本更简单时,这是过早的优化吗?当我们可以创建一个更自然的语法并具有与迭代器相同的性能时,为什么要使用迭代器呢?因为您可以通过值或常量引用传递原始迭代器,并信任编译器来优化代码。在许多情况下,编译器能够消除双重间接寻址。在不能做到这一点的情况下,性能差异几乎肯定不会真正起作用。一对迭代器已经是“您想要的浅拷贝”。而且,这是过早的优化,因为你可能在错误的地方寻找我
push        eax  // this is the constant one that has been stored in eax
lea         ecx,[ebp-24h] // ecx is the pointer to vec on the stack
call        std::vector<int,std::allocator<int> >::push_back
lea         ecx,[ebp-24h] 
push        ecx  
call        Method (8274DC0h) 
mov         ecx,dword ptr [ebp+8] 
call        std::vector<int,std::allocator<int> >::back (8276100h)
int sum(const vector<int> &v)
{
   int s = 0;
   for (auto it = v.begin(); it != v.end(); ++it) {
       s += fn(*it);
   }
   return s;
}