Возврат вектора из функции
Как правильно возвращать вектор из функции — возвращать сам вектор, указатель на него или итератор? «Правильность» интересует с точки зрения оптимального использования ресурсов и хорошего стиля программирования.
Отслеживать
371 1 1 золотой знак 5 5 серебряных знаков 13 13 бронзовых знаков
задан 2 апр 2014 в 17:10
DarkGenius DarkGenius
885 2 2 золотых знака 11 11 серебряных знаков 31 31 бронзовый знак
Передавайте в функцию ссылку на вектор, который вы собираетесь заполнять.
2 апр 2014 в 17:15
А если вектор хранится у меня как поле класса?
2 апр 2014 в 17:19
Темный гений, про RVO — правда
2 апр 2014 в 18:14
@mikillskegg, в данном случае (вектор является полем класса) это неправда.
2 апр 2014 в 19:03
@DarkGenius: Если время жизни вектора — не проблема, конечно возвращайте const ref. Основная проблема C++ и состоит, на мой взгляд, в том, что приходится чересчур много думать об аллокации/деструкции/копировании ресурсов и владении ими. Например, казалось бы, что опасного в конструкции v.push_back(v.back()); ?
5 апр 2014 в 14:30
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Давайте-ка я суммирую дискуссию в комментариях здесь.
Классическим методом является передача пустого вектора по ссылке в функцию, с тем чтобы функция его заполнила.
Затем, если вы уверены, что время жизни вектора, который вы передаёте, достаточно велико (например, вектор является полем класса), то вы можете возвращать ссылку на него. Внимание! При этом вы вводите потенциально опасную зависимость: если сам объект умрёт, ссылка на вектор тоже перестанет быт валидной! Сам объект не может знать, как его используют, и не является ли он, например, временным.
Затем, вы вполне можете вернуть вектор по значению, особенно если вы конструируете его на стеке в самой функции (и значит, не можете вернуть ссылку на него). Это кажется излишним копированием, но на самом деле оптимизатор часто может убрать ненужное копирование используя RVO/NRVO. (Вот ссылкf про это: NRVO in Visual C++ 2005). Тем не менее, иногда эта техника может всё же ведёт к копированию (например, потому, что оптимизатор не волшебник) и показывает плохие результаты.
И ещё: я бы не советовал возвращать итератор. Итераторы в C++ — ещё более хрупкая вещь, чем ссылки: любой чих в сторону вектора может его инвалидировать.
Как вернуть vector из функции: по значению или по ссылке?
Есть функция, создающая каким-то определенным образом экземпляр vector . Вопрос: как вернуть этот экземпляр вызывающему?
Правильное с точки зрения логики и стройности программы решение выглядит так:
std::vectorint> create_vector(const size_t N) std::vectorint> v; v.resize(N, 0xDEADC0DE); return v; >
Тут экземпляр вектора возвращается по значению, что означает потенциальное глубокое копирование локального объекта в контекст вызывающей функции. Сразу возникает сомнение: а что, если вектор огромен — его ж надо будет побайтно перекладывать из одного места в другое? Гораздо “разумнее” было бы написать:
void create_vector(const size_t N, std::vectorint>* v) v->resize(N, 0xDEADC0DE); >
Тут вектор передается по указателю, и стопроцентно ненужного полного копирования не будет. Но такой код выглядит откровенно плохо.
Сравним скорости работы на векторе длиной 100MB. Например, на компиляторе:
Apple clang version 3.1 (tags/Apple/clang-318.0.45) (based on LLVM 3.1svn) Target: x86_64-apple-darwin11.3.0
#include #include std::vectorint> __attribute__((noinline)) create_vector(const size_t N) std::cout <"by value" :: endl; std::vectorint> v; v.resize(N, 0xDEADC0DE); return v; > int main(int argc, char* argv[]) for (size_t i = 0; i 10; ++i) const size_t N = 1024 * 1024 * 100; std::vectorint> v = create_vector(N); if (v[i] != 0xDEADC0DE) std::cout <"Test is rubbish" :: endl; return 0; > > return 0; >
clang++ -O3 -o by_value by_value.cpp && time ./by_value
0m4.933s
Теперь по указателю:
#include #include void __attribute__((noinline)) create_vector(const size_t N, std::vectorint>* v) std::cout <"by pointer" :: endl; v->resize(N, 0xDEADC0DE); > int main(int argc, char* argv[]) for (size_t i = 0; i 10; ++i) const size_t N = 1024 * 1024 * 100; std::vectorint> v; create_vector(N, &v); if (v[i] != 0xDEADC0DE) std::cout <"Test is rubbish" :: endl; return 0; > > return 0; >
clang++ -O3 -o by_pointer by_pointer.cpp && time ./by_pointer
0m4.852s
Время в обоих случаях одинаково. Получается, что стоит выбрать первый, “красивый” вариант.
Объяснений тут два. Первый, и возможно самый важный — это RVO, Return value optimization. Это когда компилятор догадывается, что создаваемый локальный экземпляр вектора предназначен для возврата из функции, и сразу создает его в контексте вызывающего кода, чтобы потом не копировать туда. Фактически компилятор реализует передачу по ссылке, но неявно, не портя красоту исходного кода. Данный трюк будет работать для любого класса, не обязательно класса из STL.
Но оптимизация — это негарантированная вещь. Но тут есть еще одно подспорье. Стандартные контейнеры STL реализованы так, что при даже при глубоком копировании фактически копируется только небольшая управляющая структура, а сами данные, размещенные в куче, просто перебрасываются указателем, без их фактического перемещения. Тут, конечно, будет небольшое дополнительное копирование, но оно минимально, и возможно на него стоит пойти ради сохранения красивого кода.
Ну а в контексте C++11, где есть семантика перемещения, вообще не будет лишних копирований, если класс “правильно” реализован (что верно для классов из STL).
Мораль: используйте по возможности контейнеры из STL и оставьте оптимизацию компилятору. Иногда, конечно, компилятор ошибается, но таких случаев гораздо меньше, чем наоборот.
Как приавильно в функции вернуть vector (C++ Builder)
В билдере работаю не давно, до этого в Дельфях кодил. Объясните как вернуть вектор функцией, которая должна разбить строку по переданному ей делителю?
Ругается на строку возвращения ветора «Undefined symbol ‘prm’«.
vector < AnsiString >explode ( AnsiString str, AnsiString delitel ) <
int len = str. Length ( ) ;
AnsiString search,simbol ;
for ( int i = 1 ; i search = str. SubString ( i, 1 ) ;
if ( search ! = delitel ) <
simbol = simbol + «» + search ;
> else
<
vector < AnsiString >prm ;
prm. push_back ( simbol ) ;
simbol = «» ;
>
>
return prm ;
>
Как вернуть несколько значений из функции?
Здравствуйте, пытаюсь написать функцию для генерации чисел, и надо вернуть 3 переменные из функции. Но получается вернуть только одну. Как вернуть несколько переменных из функции в С++?
#include #include using namespace std; int generator() < random_device random_device; mt19937 generator(random_device()); uniform_int_distribution<>distribution(1, 10); int x = distribution(generator); int c = distribution(generator); int answer = x * c; return answer, x, c; > int main()
- Вопрос задан более года назад
- 697 просмотров
Комментировать
Решения вопроса 1
Wataru @wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Кроме параметров функции, можно возвращать структуру с именованными значениями или std::vector или std::touple.
Ответ написан более года назад
Нравится 2 6 комментариев
Anonymous @Nikita1244 Автор вопроса
Проблема в том, что предложенное ранее решение не работает.
Про ваше решение можете немного понятнее объяснить, пожалуйста?
Wataru @wataru Куратор тега C++
Никита Савченко, Чего там не работает-то?
Тип функции сделайте s td::vector . Возвращайте
Anonymous @Nikita1244 Автор вопроса
Wataru, я как новичок. Не пойму все равно, что означает ваш std::vector? Погуглил, не понял.
Wataru @wataru Куратор тега C++
Никита Савченко, Это тип. Как int, bool, или char*. std::vector — это класс, который хранит массив int в c++.
Anonymous @Nikita1244 Автор вопроса
Wataru, спасибо, попробую обязательно.
Anonymous @Nikita1244 Автор вопроса
Дошли руки посмотреть Ваше решение, и действительно, решение Александра Ананьева выглядит не элегантно, и оно устарело(Использовалось в последний раз как не-легаси код, судя по всему, в С++ 11).
Сейчас действительно используют std::vector, std::touple
Ответы на вопрос 1
Можно вернуть через параметры функции
void generator(int& answer, int& x, int& c)
#include #include using namespace std; void generator(int& answer, int& x, int& c) < random_device random_device; mt19937 generator(random_device()); uniform_int_distribution<>distribution(1, 10); x = distribution(generator); c = distribution(generator); answer = x * c; > int main()
Ответ написан более года назад
Нравится 2 11 комментариев
Anonymous @Nikita1244 Автор вопроса
возвращает в любом случае тогда 1. И ничего более.
Anonymous @Nikita1244 Автор вопроса
не работает в общем-то ваше решение, к сожалению 🙁
Никита Савченко, оно не может не работать. Покажи свой код.
Anonymous @Nikita1244 Автор вопроса
#include #include using namespace std; void generator(int& answer, int& x, int& c) < random_device random_device; mt19937 generator(random_device()); uniform_int_distribution<>distribution(1, 10); int x = distribution(generator); int c = distribution(generator); int answer = x * c; return answer, x, c; > int main()
Евгений Шатунов @MarkusD Куратор тега C++
не работает в общем-то ваше решение
Да нет, решение работает. Только Саша, по своему обыкновению, не подумав написал первое что ему в гугле попалось.
При передаче по ссылке тебе не надо возвращать значения, они доступны внутри функции для прямого изменения. Это — сублимация возврата нескольких значений, используемая в палеозойской древности стандарта C++98.
Настоящий возврат набора значений описан только в ответе Wataru.
Но вот в чем дело.
Код return answer, x, c; не должен был родиться в твоем мозгу для функции с типом результата void . Написанное тобой в примере выдает твою полную безграмотность в языке. Даже большую, чем у Саши.
И в этом плане настоящий возврат нескольких значений для тебя просто недосягаем, потому что ты просто не проходишь по знаниям для его использования. Ты ведь даже не знаешь, какой конкретно стандарт C++ ты используешь. А ведь именно от этого зависит решение твоего вопроса.
#include #include using namespace std; void generator(int& answer, int& x, int& c) < random_device random_device; mt19937 generator(random_device()); uniform_int_distribution<>distribution(1, 10); x = distribution(generator); c = distribution(generator); answer = x * c; > int main()
Anonymous @Nikita1244 Автор вопроса
Александр Ананьев, Отредактируй немного ответ, добавив этот код(пусть чисто пример будет в ответе), чтобы людям не копаться в комментариях. Отмечу решением.
Anonymous @Nikita1244 Автор вопроса
Ты ведь даже не знаешь, какой конкретно стандарт C++ ты используешь.
Никогда не думал, что стандарты влияют настолько сильно. Стандарт использую — С++ 17. Хорошо, посмотрю ответ Wataru. Как я понял, решение, которое написал Александр Ананьев, является устаревшим.
Anonymous @Nikita1244 Автор вопроса
Код return answer, x, c; не должен был родиться в твоем мозгу для функции с типом результата void
Тут уже мой косяк, да, но я удалял и код все равно не работал 🙂
Только вот я пробовал по разному писать и всю эту чепуху вам прислал. Так-то да, ретурна судя по всему не должно быть, ибо компилятор и IDE ругаются на него и говорят что ретурна не может быть в void-функции.
Евгений Шатунов @MarkusD Куратор тега C++
Стандарт использую — С++ 17.
Да, стандарт языка кардинально влияет на доступные возможности. Смотри вот.
Допустим у тебя есть твоя std::tuple generator() . Это то, о чем написал Wataru. Принять результат ты можешь в такой же кортеж (C++11), можешь воспользоваться обобщением и инициализацией копией (C++11), а можешь воспользоваться std::tie [?] для C++14 или структурным связыванием из C++17.
И все будет зависеть именно от используемого тобой стандарта языка.
И вот, в C++17 у тебя получится такой код:
int main()
При том условии, что ты правильно воспользуешься техникой NRVO, структурное связывание у тебя не приведет к скрытому копированию значения, а все три поля из main будут сразу инициализированы внутри generator .