C/ c++:使用函数返回的向量的有效方法

C/C++: efficient way to use a vector returned by a function

本文关键字:返回 向量 有效 方法 函数 c++      更新时间:2023-10-16

假设有一个类型为vector<int>的向量V,它是一个类的私有成员。

我们还有这个类的公共函数:

 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()时都复制向量?

如果没有复制发生,那么我相信第一个版本更高效。

然而,我不知道到底发生了什么。

我想如果我们返回一个对向量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);
 }

然而,我想知道第一种情况到底发生了什么。有什么要复制的吗?即使在第三种情况下,我们也返回一个指针,所以这里发生了某种复制。

提前感谢

原则上,在第一种情况下,您接收整个向量的副本,对其调用size(),然后它立即超出作用域。

在实践中,这是非常常见的,现代编译器可能能够识别它并完全优化复制。例如,你可以在这里阅读更多相关内容。要知道机器上发生了什么,唯一的方法就是读取编译后的汇编代码。编辑:或者像Named那样执行堆栈跟踪。:)

在第三种情况下,您唯一复制的是指针的值,该值为4或8字节(在64位操作系统上为8字节)。

如果你担心效率,最好的办法就是:两种方法都试一试,看看哪一种更快。

第一种情况非常糟糕,因为它可以多次复制向量。编译器可能会优化(或不)您的代码并隐藏此问题(这取决于您使用的编译器)。最好的解决方案是定义一个返回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);

没有考虑可能的编译器优化,第一个版本在每次迭代时创建整个vector的副本作为返回值。效率非常低。

我不认为RVO在这里是可能的,因为V是类成员而不是独立变量。

这里是正在发生的事情的一个示例。从跟踪器输出的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

你可以看到整个vector(3个元素)在每次迭代中都被复制。

---------------------------------------------------------------------------------------------

更好的实现是返回对vector的引用。

 vector<int>& getV(){ return V; }
           ^^^

现在你不能复印了。这里是在这个版本中发生的。这是跟踪器的输出。

starting loop
[(none)]    Tracer& Tracer::operator=(const Tracer&)
[(none)]    Tracer& Tracer::operator=(const Tracer&)
[(none)]    Tracer& Tracer::operator=(const Tracer&)
Ending loop

有两种不同的说法。一个启用了优化,另一个禁用了优化。这篇文章想要速度吗?

如何通过继承扩展您的类,然后您可以在MyClass中使用所有STL算法。您将MyClass定义为序列容器的扩展,之后继承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进行排序,等等。