Как мне вернуть сотни значений из функции C ++? - PullRequest
4 голосов
/ 25 февраля 2009

В C ++ всякий раз, когда функция создает много (сотни или тысячи) значений, я использовал, чтобы вызывающая сторона передавала массив, который моя функция затем заполняет выходными значениями:

void computeValues(int input, std::vector<int>& output);

Итак, функция заполнит вектор output значениями, которые она вычисляет. Но это не совсем хороший стиль C ++, как я сейчас понимаю.

Следующая сигнатура функции лучше, потому что она не обязывает использовать std::vector, но может использовать любой контейнер:

void computeValues(int input, std::insert_iterator<int> outputInserter);

Теперь звонящий может позвонить с помощью inserter:

std::vector<int> values; // or could use deque, list, map, ...
computeValues(input, std::back_inserter(values));

Опять же, мы не обязуемся использовать конкретно std::vector, что хорошо, потому что пользователю могут просто понадобиться значения в std::set и т. Д. (Должен ли я передать iterator по значению или по ссылке? )

Мой вопрос: insert_iterator правильный или стандартный способ сделать это? Или есть что-то еще лучше?

РЕДАКТИРОВАТЬ: Я отредактировал вопрос, чтобы было ясно, что я говорю не о возврате двух или трех значений, а о сотнях или тысячах. (Представьте, что вы вернули все файлы, которые вы нашли в определенном каталоге, или все ребра на графике и т. Д.)

Ответы [ 9 ]

7 голосов
/ 25 февраля 2009

Ответ на редактирование: Ну, если вам нужно вернуть сотни и тысячи значений, конечно, кортеж не будет подходящим способом. Лучше всего выбрать решение с помощью итератора, но лучше не использовать какой-либо конкретный тип итератора.


Если вы используете итераторы, вы должны использовать их как можно более универсально. В своей функции вы использовали итератор вставки, например insert_iterator< vector<int> >. Вы потеряли любую щедрость. Сделайте это так:

template<typename OutputIterator>
void computeValues(int input, OutputIterator output) {
    ...
}

Что бы вы ни дали, это будет работать сейчас. Но это не будет работать, если у вас есть разные типы в возвращаемом наборе. Тогда вы можете использовать кортеж. Также доступно как std::tuple в следующем стандарте C ++:

boost::tuple<int, bool, char> computeValues(int input) { 
    ....
}

Если количество значений варьируется, а тип значений из фиксированного набора, например (int, bool, char), вы можете посмотреть контейнер boost::variant. Это, однако, подразумевает изменения только на стороне вызова. Вы можете сохранить стиль итератора выше:

std::vector< boost::variant<int, bool, char> > data;
computeValues(42, std::back_inserter(data));
6 голосов
/ 25 февраля 2009

Вы можете вернуть умный указатель на вектор. Это должно работать, и копия вектора не будет сделана.

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

3 голосов
/ 25 февраля 2009

На самом деле, ваш старый метод передачи в векторе может многое порекомендовать - он эффективен, надежен и прост для понимания. Недостатки реальны, но не применяются одинаково во всех случаях. Люди действительно хотят получать данные в std :: set или list? Действительно ли они захотят использовать длинный список чисел, не удосужившись сначала присвоить его переменной (одна из причин для возврата чего-либо через «возврат», а не параметр)? Быть универсальным - это хорошо, но затрачиваемое на программирование время не может быть погашено.

2 голосов
/ 25 февраля 2009

Если у вас когда-либо есть группа объектов, скорее всего, у вас есть хотя бы несколько методов, которые работают с этой группой объектов (в противном случае, что вы делаете с ними?)

Если это так, то имеет смысл иметь те методы в классе, которые содержат как указанные объекты, так и методы.

Если это имеет смысл, и у вас есть такой класс, верните его.

Я практически никогда не задумываюсь над тем, что хотел бы вернуть более одного значения. Из-за того, что метод должен делать только одну маленькую вещь, ваши параметры и возвращаемые значения имеют тенденцию иметь отношения, и поэтому чаще всего они не заслуживают класса, который их содержит, поэтому возвращение более одного значения редко представляет интерес (возможно, Я желал этого 5 раз за 20 лет - каждый раз, когда я вместо этого проводил рефакторинг, получал лучший результат и понимал, что моя первая попытка не соответствует стандартам.)

1 голос
/ 25 февраля 2009

Я бы использовал что-то вроде

std::auto_ptr<std::vector<int> > computeValues(int input);
{
   std::auto_ptr<std::vector<int> > r(new std::vector<int>);
   r->push_back(...) // Hundreds of these
   return r;
}

Отсутствие копирования при возврате или риск утечки (если вы правильно используете auto_ptr в вызывающей стороне).

1 голос
/ 25 февраля 2009

Ваш пример с insert_iterator не будет работать, потому что insert_iterator - это шаблон, для которого требуется контейнер для параметра. Вы могли бы объявить это

void computeValues(int input, std::insert_iterator<vector<int> > outputInserter);

или

template<class Container>
void computeValues(int input, std::insert_iterator<Container> outputInserter);

Первый свяжет вас с векторной реализацией, без каких-либо явных преимуществ перед вашим исходным кодом. Второе менее ограничительно, но реализация в качестве шаблона даст вам другие ограничения, которые могут сделать его менее желательным выбором.

1 голос
/ 25 февраля 2009
  • Стандартный контейнер работает для однородных объектов 9, которые можно вернуть).
  • Стандартный способ библиотеки - абстрагировать алгоритм от контейнера и использовать итераторы для преодоления разрыва между ними.
  • Если вам нужно пройти более одного типа, подумайте о структурах / классах.

Мой вопрос: является ли insert_iterator правильным или стандартным способом сделать это?

Да. В противном случае, если в вашем контейнере не будет хотя бы столько элементов, сколько будет вычисляемых значений. Это не всегда возможно, особенно если вы хотите записать в поток. Итак, вы хороши.

1 голос
/ 25 февраля 2009

Еще одна опция - это boost :: tuple: http://www.boost.org/doc/libs/1_38_0/libs/tuple/doc/tuple_users_guide.html

int x, y;
boost::tie(x,y) = bar();
0 голосов
/ 25 февраля 2009

Я бы сказал, что ваше новое решение носит более общий и лучший стиль. Я не уверен, что беспокоился бы слишком о стиле в c ++, о юзабилити и эффективности.

Если вы возвращаете много предметов и знаете размер, использование вектора позволит вам зарезервировать память за одно выделение, что может стоить или не стоить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...