C/C++:使用函数返回的向量的有效方法
Posted
技术标签:
【中文标题】C/C++:使用函数返回的向量的有效方法【英文标题】:C/C++: efficient way to use a vector returned by a function 【发布时间】:2013-05-02 09:02:56 【问题描述】:假设我们有一个名为 V
类型为 vector<int>
的向量,它是类的私有成员。
我们也有这个类的公共函数:
vector<int> getV() return V;
现在,如果我有这个类的一个实例,我想做的就是读取值并找到向量内所有值的总和,
我可以这样说:
MyClass obj;
//update vector
size_t i, size;
size = obj.getV().size();
int sum = 0;
for(size_t i = 0; i < size; i++)
sum += obj.getV().at(i);
或者我可以这样说:
MyClass obj;
//update vector
size_t i, size;
vector<int> myV = obj.getV();
size = myV.size();
int sum = 0;
for(size_t i = 0; i < size; i++)
sum += myV[i];
在第二种情况下,我们将整个向量复制到向量myV
。但是,我不确定在第一种情况下到底发生了什么,我们是按原样使用向量,还是每次调用函数getV()
时都实际复制向量?
如果没有复制,那么我相信第一个版本效率更高。
但我并不是 100% 知道到底发生了什么。
如果我们返回对向量V
的引用,我想我们可以完全避免进行任何复制。所以我们可以有以下功能:
vector<int>* getV return &V;
然后
MyClass obj;
//update vector
size_t i, size;
vector<int> * myV = obj.getV();
size = myV->size();
int sum = 0;
for(size_t i = 0; i < size; i++)
sum += myV->at(i);
但是我想知道在第一种情况下到底发生了什么。有什么被复制的吗?即使在第三种情况下,我们也会返回一个指针,因此会发生某种复制。
提前谢谢你
【问题讨论】:
另一种选择是制作一个以索引为参数并返回向量对应值的函数。类似int getValue( int index ) reutrn v[index];
参考vectorbegin()
和end()
迭代器(或const_iterators)。
您似乎只想从向量中读取,因此返回一个 const 引用就足够了,或者您可以更通用一些并返回一个包含开始和结束的 std::pair迭代器(当然都是 const )。这样可以让你改变内部数据结构,读取更灵活。
【参考方案1】:
原则上,在第一种情况下,您会收到整个向量的副本,对其调用 size(),然后它会立即超出范围。
在实践中,这很常见,以至于现代编译器可能能够识别它并完全优化副本。 You can read more about this here, for example. 了解机器上发生的事情的唯一方法是阅读编译后的汇编代码。编辑:或者像 Named 那样做一个堆栈跟踪。 :)
在第三种情况下,您要复制的唯一内容是指针的值,它是 4 或 8 个字节(在 64 位操作系统上为 8 个字节)。
如果您担心效率,最好的办法始终是:双向尝试,看看哪个更快。
【讨论】:
【参考方案2】:第一种情况非常糟糕,因为它可以多次复制您的向量。编译器可能会优化(或不优化)您的代码并隐藏此问题(这取决于您使用的编译器)。最好的解决方案是定义一个返回 const 引用的方法,如
const std::vector<int> & getV() const return V;
并使用以下代码
const vector<int> & myV = obj.getV();
int sum = 0;
for(size_t i = 0, size = myV.size(); i < size; ++i)
sum += myV[i];
顺便说一下,对向量求和的代码可以替换为:
int sum = std::accumulate(myV.begin(), myV.end(), 0);
【讨论】:
【参考方案3】:不考虑可能的编译器优化,第一个版本在每次迭代时创建整个向量的副本作为返回值。这是非常低效的。
我认为 RVO 在这里不可能,因为 V 是类成员而不是独立变量。
Here 是正在发生的事情的一个例子。来自 3 个元素的向量的跟踪器输出。
starting loop
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer& Tracer::operator=(const Tracer&)
[(none)] Tracer::~Tracer()
[(none)] Tracer::~Tracer()
[(none)] Tracer::~Tracer()
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer& Tracer::operator=(const Tracer&)
[(none)] Tracer::~Tracer()
[(none)] Tracer::~Tracer()
[(none)] Tracer::~Tracer()
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer::Tracer(const Tracer&)
[(none)] Tracer& Tracer::operator=(const Tracer&)
[(none)] Tracer::~Tracer()
[(none)] Tracer::~Tracer()
[(none)] Tracer::~Tracer()
Ending loop
如您所见,每次迭代都会复制整个向量(3 个元素)。
----------------------------------------------- ----------------------------------------------
更好的实现是返回对向量的引用。
vector<int>& getV() return V;
^^^
现在您不会制作任何副本。 Here 是这个版本发生的事情。这是追踪器的输出。
starting loop
[(none)] Tracer& Tracer::operator=(const Tracer&)
[(none)] Tracer& Tracer::operator=(const Tracer&)
[(none)] Tracer& Tracer::operator=(const Tracer&)
Ending loop
【讨论】:
【参考方案4】:有两个不同的故事要讲。一个启用优化,另一个禁用优化。这篇文章Want Speed? Pass by Value 可能会有所启发。
【讨论】:
【参考方案5】:如何通过继承来扩展你的类,然后你可以使用 MyClass 的所有 STL 算法。您将 MyClass 定义为 Sequence Container 的扩展,然后继承 Sequence 公共接口,您的对象可以通过 STL 算法进行操作。编写自己的循环是可以的,但充分使用 STL 将产生更易读和更易于维护的代码,您只需要在使用算法时小心以确保效率(例如使用范围成员函数与单元素函数) .
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <numeric>
template
<
typename Type,
template
<
typename Element,
typename Allocator=std::allocator<Element>
> class Sequence
>
class MyClass
:
public Sequence<Type>
public:
MyClass()
:
Sequence<Type>()
template<typename Iterator>
MyClass(Iterator begin, Iterator end)
:
Sequence<Type>(begin, end)
;
template<typename Type>
class add_element
Type const& t_;
public:
add_element(Type const& t)
:
t_(t)
template<typename Element>
void operator()(Element & lhs)
lhs += t_;
;
using namespace std;
int main(int argc, const char *argv[])
MyClass<int, vector> m;
m.push_back(0);
m.push_back(1);
m.push_back(2);
m.push_back(3);
copy(m.begin(), m.end(), ostream_iterator<int>(cout, " "));
cout << endl;
for_each(m.begin(), m.end(), add_element<int>(-10));
copy(m.begin(), m.end(), ostream_iterator<int>(cout, " "));
cout << endl;
MyClass<int,vector>::value_type sum = accumulate(m.begin(), m.end(), 0);
cout << "sum = " << sum << endl;
return 0;
输出:
0 1 2 3
-10 -9 -8 -7
sum = -34
同样,您现在可以使用 std::accumulate 计算元素的总和,使用 std::sort 对 MyObject 进行排序等。
【讨论】:
以上是关于C/C++:使用函数返回的向量的有效方法的主要内容,如果未能解决你的问题,请参考以下文章