При возврате контейнера мне всегда приходилось определять, использовать ли мне возвращаемое значение или использовать выходной параметр. Если производительность имеет значение, я выбрал второй вариант, в противном случае я всегда выбирал первый вариант, потому что он более интуитивно понятен.
Честно говоря, лично у меня были серьезные возражения по поводу выходных параметров, возможно, из-за моей математической подготовки, но было вполне нормально использовать их, когда у меня не было других вариантов.
Однако, когда дело доходит до общего программирования, вещи полностью изменились. Я сталкивался с ситуациями, когда функция может не знать, является ли возвращаемый объект огромным контейнером или просто простым значением.
Последовательное использование выходных параметров может быть решением, которое я хочу знать, если я могу избежать. Это так неловко, если я должен сделать
int a;
f(a, other_arguments);
по сравнению с
auto a = f(other_arguments);
Кроме того, иногда возвращаемый тип f()
не имеет конструктора по умолчанию. Если используются выходные параметры, то нет изящного способа справиться с этим случаем.
Интересно, возможно ли вернуть «объект-модификатор», функтор, принимающий выходные параметры для их соответствующего изменения. (Возможно, это своего рода ленивая оценка?) Ну, возвращать такие объекты не проблема, но проблема в том, что я не могу вставить соответствующую перегрузку оператора присваивания (или конструктора), который берет такой объект и вызывает его для делать свою работу, когда возвращаемый тип принадлежит библиотеке, к которой я не могу прикоснуться, например, std::vector
, Конечно, операторы преобразования бесполезны, поскольку у них нет доступа к существующим ресурсам, подготовленным для целевого объекта.
Некоторые люди могут спросить, почему бы не использовать assign()
; определить «объект генератора», который имеет begin()
& end()
и передать эти итераторы std::vector::assign
, Это не решение. По первой причине «объект-генератор» не имеет полного доступа к целевому объекту, и это может ограничить то, что может быть сделано. По второй и более важной причине сайт вызова моей функции f()
также может быть шаблоном, который не знает точный тип возвращаемого значения f()
поэтому он не может определить, какой из операторов присваивания или assign()
функция-член должна быть использована.
Я думаю, что подход «объект-модификатор» для модификации контейнеров уже должен был обсуждаться в прошлом, так как это вовсе не новая идея.
Подводить итоги,
Пример кода, который я привел выше, вводит в заблуждение. Функция f()
может использоваться для инициализации локальной переменной, но также может использоваться для изменения существующих переменных, определенных в другом месте. В первом случае, как упомянул Rakete1111, нет проблем с возвратом по значению, поскольку в игру вступает разрешение на копирование. Но для второго случая может быть ненужное освобождение / получение ресурсов.
Я не думаю, что ваш «объект-модификатор» когда-либо предлагался (AFAIK). И это никогда не войдет в стандарт. Зачем? Потому что у нас уже есть способ избавиться от дорогой копии, и это возврат по значению (плюс оптимизация компилятора).
До C ++ 17 компиляторам было разрешено делать практически одно и то же. Эта оптимизация известна как (N) RVO, которая оптимизирует удаление копии при возврате (именованного) временного объекта из функции.
auto a = f(other_arguments);
Не вернет временный, затем скопируйте его в a
, Компилятор полностью оптимизирует копию, это не нужно. Теоретически, вы не можете предполагать, что ваш компилятор поддерживает это, но три основных из них (clang, gcc, MSVC) это делают, поэтому не нужно беспокоиться — я не знаю о ICC и других, поэтому не могу сказать.
Таким образом, поскольку здесь не используется копирование (или перемещение), нет потери производительности при использовании возвращаемых значений вместо выходных параметров (скорее всего, если по какой-то причине ваш компилятор не поддерживает его, вы получите большую часть время). Вы всегда должны использовать возвращаемые параметры, если это возможно, и использовать выходные параметры или какой-либо другой метод, если вы измеряете, что в противном случае вы получите значительно лучшую производительность.
(Отредактировано, основываясь на комментариях)
Вы правы, вы должны избегать выходных параметров, если это возможно, потому что код, использующий их, труднее читать и отлаживать.
Начиная с C ++ 11 у нас есть функция под названием переместить конструкторы (см. ссылку). Ты можешь использовать std::move
на всех типах примитивов и контейнеров STL. Это эффективно (вопрос: Разница в эффективности между конструктором копирования и перемещения), потому что вы на самом деле не копируете значения переменных. Меняются только указатели. Для ваших собственных сложных типов вы можете написать свой собственный конструктор перемещения.
С другой стороны, единственное, что вы можете сделать, это вернуть ссылку на временный объект, который имеет неопределенное поведение, например:
#include <iostream>
int &&add(int initial, int howMany) {
return std::move(initial + howMany);
}
int main() {
std::cout << add(5, 2) << std::endl;
return 0;
}
Его выход мог быть:
7
Вы можете избежать проблемы временных переменных, использующих глобальные или статические переменные, но это тоже плохо. @Rakete прав, хорошего способа добиться этого нет.