Страницы

Поиск по вопросам

воскресенье, 5 января 2020 г.

Возврат вектора из функции

#vector #cpp #функции


Как правильно возвращать вектор из функции - возвращать сам вектор, указатель на
него или итератор? "Правильность" интересует с точки зрения оптимального использования
ресурсов и хорошего стиля программирования.    


Ответы

Ответ 1



Давайте-ка я суммирую дискуссию в комментариях здесь. Классическим методом является передача пустого вектора по ссылке в функцию, с тем чтобы функция его заполнила. Затем, если вы уверены, что время жизни вектора, который вы передаёте, достаточно велико (например, вектор является полем класса), то вы можете возвращать ссылку на него. Внимание! При этом вы вводите потенциально опасную зависимость: если сам объект умрёт, ссылка на вектор тоже перестанет быт валидной! Сам объект не может знать, как его используют, и не является ли он, например, временным. Затем, вы вполне можете вернуть вектор по значению, особенно если вы конструируете его на стеке в самой функции (и значит, не можете вернуть ссылку на него). Это кажется излишним копированием, но на самом деле оптимизатор часто может убрать ненужное копирование используя RVO/NRVO. (Вот ссылкf про это: NRVO in Visual C++ 2005). Тем не менее, иногда эта техника может всё же ведёт к копированию (например, потому, что оптимизатор не волшебник) и показывает плохие результаты. И ещё: я бы не советовал возвращать итератор. Итераторы в C++ — ещё более хрупкая вещь, чем ссылки: любой чих в сторону вектора может его инвалидировать.

Ответ 2



Возврат итератора требует фактического хранения вектора после завершения работы. Т.е. либо вектор должен быть статичным (объявлен с ключевым словом static), либо как член класса, а возвращать его будет метод, либо получен на вход функции ещё откуда-либо. С одним лишь итератором многого не сделать: неизвестно, например, сколько ещё элементов после текущего итератора можно прочитать (для этого придётся возвращать пару итераторов, итератор и длину остатка или что-то подобное), нельзя вставить новый элемент или удалить существующий. С другой стороны, итератор концептуально представляет собою позицию в контейнере (плюс вариант отсутствия когда итератор указывает на end). Итого: если вам нужна именно семантика указателя на позицию в контейнере, итератор вам подойдёт. В остальных случаях ничего хорошего не выйдет. Возврат указателя можно разделить на 2 случая: с передачей владения и без (т.е. определить, кто занимается удалением вектора). Если владение не передаётся, остаются всё те же требования к времени жизни рассматриваемого вектора, как и в предыдущем пункте. В таком случае можно также вернуть ссылку на вектор — подходы будут эквивалентны. class Node { std::vector children_; // ... public: // ... const std::vector& getChildren() const { return children_; // ОК: возвращаемый вектор имеет такое же время жизни, как // и объект, с которым мы работаем } } Если нужно передать владение вектором вызывающему контексту, следует изначально создавать его на куче (с помощью оператора new). С точки зрения идиоматичного C++ следует использовать умные указатели shared_ptr, unique_ptr или их аналоги из библиотек вроде boost или Qt. std::shared_ptr> getNumbers(int n) { auto res = make_shared>(n); // ... return res; } Следует заметить, что в C++11 такой подход довольно бессмыслен, поскольку там существует семантика перемещения (об этом позже). С другой стороны, shared_ptr доступен и в C++03 с TR1. Правда, он может быть не совсем эквивалентен своему собрату из 11 стандарта. Возврат по значению может вылиться в дорогостоящее копирование, однако: В C++11 (который на текущий момент с нами уже 4 года, между прочим) существует семантика перемещения, которая позволит вернуть вектор по значению без копирования. Даже для более старых стандартов многие компиляторы реализуют оптимизации Return Value Optimization и Named Return Value Optimization (подробнее о этих товарищах можно почитать у Алёны C++). Несмотря на то, что большинство компиляторов поддерживают эти оптимизации, могут существовать экзотические / старые компиляторы, не проводящие их. Следует отметить, что эта оптимизация не всегда срабатывает. Один из таких случаев — когда возвращаемый результат зависит от пути исполнения, например std::string toString(bool flag) { std::string a("True"); std::string b("False"); return flag ? a : b; }

Ответ 3



Например, так: void MagicMethod(std::vector &destVector) { std::vector tmpVector; //что-то делаете с tmpVector ... destVector.swap(tmpVector); //вернули вектор }

Комментариев нет:

Отправить комментарий