3

假设我们有一个称为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% 到底发生了什么。

我想如果我们返回对 vector 的引用,我们可以完全避免进行任何复制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);
 }

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

先感谢您

4

5 回答 5

3

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

在实践中,这非常普遍,以至于现代编译器可能能够识别它并完全优化副本。例如,您可以在此处阅读更多相关信息。了解您的机器上发生了什么的唯一方法是阅读已编译的汇编代码。编辑:或者像 Named 那样做一个堆栈跟踪。:)

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

如果您担心效率,最好的办法始终是:尝试两种方式,看看哪个更快。

于 2013-05-08T08:14:23.017 回答
1

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

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

我不认为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

如您所见,每次迭代都会复制整个向量(3 个元素)。

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

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

 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
于 2013-05-08T08:12:25.383 回答
1

会有两个不同的故事要讲。一个启用优化,另一个禁用优化。这篇文章想要速度?按价值传递可能会有所启发。

于 2013-05-08T08:23:06.323 回答
-1

如何通过继承来扩展你的类,然后你可以使用 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 进行排序等。

于 2013-05-08T08:11:44.753 回答