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]; 参考vector文档——operator =和copy constructor @olevegard 或者提供两个函数返回向量的begin()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++:使用函数返回的向量的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

C语言中的main()函数返回值是啥?

关于三维向量加减乘除的C语言宏定义?

C++ 从函数调用的多次返回中构建字符串向量的最佳方法

C语言都有哪些取整函数?

C/C++ 需要一种聪明的方法来跟踪函数调用

C语言中函数可以返回哪些类型的数据?(求详解)