C / C ++: эффективный способ использовать вектор, возвращаемый функцией

Предположим, у нас есть вектор с именем 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);
}

Однако хотелось бы узнать, что именно происходит в первом случае. Что-нибудь копируется? Даже в третьем случае мы возвращаем указатель, поэтому происходит какое-то копирование.

заранее спасибо

3

Решение

В принципе, в первом случае вы получаете копию всего вектора, вызывая для него функцию size (), а затем она сразу выходит из области видимости.

На практике это настолько распространено, что современные компиляторы могут распознать его и полностью оптимизировать копию. Вы можете прочитать больше об этом здесь, например. Единственный способ узнать, что происходит на вашем компьютере, — это прочитать скомпилированный код сборки. РЕДАКТИРОВАТЬ: или сделать трассировку стека, как это сделал Named. 🙂

В третьем случае единственное, что вы копируете, это значение указателя, которое составляет 4 или 8 байтов (8 в 64-битной операционной системе).

Если вы беспокоитесь об эффективности, лучшее, что нужно сделать, это всегда: попробуйте оба способа и посмотрите, что быстрее.

3

Другие решения

Первый случай очень плох, потому что он может скопировать ваш вектор несколько раз. Компилятор может оптимизировать (или нет) ваш код и скрыть эту проблему (это зависит от используемого вами компилятора). Лучшее решение — определить метод, который возвращает константную ссылку, такую ​​как

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);
1

Не учитывая возможные оптимизации компилятора, первая версия создает копию всего вектора на каждой итерации как возвращаемое значение. Что очень неэффективно.

Я не думаю РВО здесь возможно, так как 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
1

Там будут две разные истории, чтобы рассказать. Один с включенной оптимизацией, а другой с отключенной оптимизацией. Эта статья Хотите скорость? Передать по значению может пролить немного света.

1

Как насчет расширения вашего класса наследованием, тогда вы можете использовать все алгоритмы STL с MyClass. Вы определяете MyClass как расширение контейнера последовательностей, после чего наследуете открытый интерфейс последовательности, и ваш объект может работать с алгоритмами 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 :: аккумулированием для вычисления суммы элементов, std :: sort для сортировки MyObject и т. Д.

-1
По вопросам рекламы [email protected]