2010-01-26 2 views
8

Я видел многочисленные аргументы, что использование возвращаемого значения предпочтительнее для параметров. Я убежден в причинах, почему их избегают, но я не уверен, сталкиваюсь ли я с ситуациями, когда это неизбежно.Как избежать параметров?

Часть первая моего вопроса: Каковы некоторые из ваших любимых/распространенных способов обойти использование параметра out? Материал в строках: Человек, в экспертных обзорах, я всегда вижу, что другие программисты делают это, когда они могли бы легко сделать это таким образом.

Часть вторая моего вопроса касается некоторых конкретных случаев, с которыми я столкнулся, когда бы хотел избежать параметра out, но не могу придумать, как это сделать.

Пример 1: У меня есть класс с дорогой копией, которую я бы хотел избежать. Работа над объектом может быть выполнена, и это создает дорогостоящий объект для копирования. Работа по созданию данных также не является тривиальной. В настоящее время я передам этот объект в функцию, которая изменит состояние объекта. Это для меня предпочтительнее вводить объект, внутренний для рабочей функции, и возвращать его обратно, поскольку он позволяет мне хранить вещи в стеке.

class ExpensiveCopy //Defines some interface I can't change. 
{ 
public: 
    ExpensiveCopy(const ExpensiveCopy toCopy){ /*Ouch! This hurts.*/ }; 
    ExpensiveCopy& operator=(const ExpensiveCopy& toCopy){/*Ouch! This hurts.*/}; 

    void addToData(SomeData); 
    SomeData getData(); 
} 

class B 
{ 
public: 
    static void doWork(ExpensiveCopy& ec_out, int someParam); 
    //or 
    // Your Function Here. 
} 

Используя свою функцию, я получаю код вызова, как это:

const int SOME_PARAM = 5; 
ExpensiveCopy toModify; 
B::doWork(toModify, SOME_PARAM); 

Я хотел бы иметь что-то вроде этого:

ExpensiveCopy theResult = B::doWork(SOME_PARAM); 

Но я, если это не знаю возможно.

Второй пример: У меня есть массив объектов. Объекты в массиве являются сложным типом, и мне нужно выполнить работу над каждым элементом, работа, которую я хотел бы отделить от основного цикла, который обращается к каждому элементу. Код в настоящее время выглядит так:

std::vector<ComplexType> theCollection; 
for(int index = 0; index < theCollection.size(); ++index) 
{ 
    doWork(theCollection[index]); 
} 

void doWork(ComplexType& ct_out) 
{ 
    //Do work on the individual element. 
} 

Любые предложения относительно того, как бороться с некоторыми из этих ситуаций? Я работаю в основном на C++, но мне интересно узнать, облегчают ли другие языки более легкую настройку. Я встречал RVO как возможное решение, но мне нужно больше узнать об этом, и это похоже на специфику компилятора.

+0

Семантики неконстантного опорного параметра, что он будет (может) быть изменен в функции. Если бы вы не собирались менять его, вы бы передали его как ссылку на const. – Bill

+0

* «но мне интересно узнать, облегчают ли другие языки более легкую настройку» * - такие языки, как Java и C#, просто не имеют этой проблемы, поскольку они всегда ссылаются на объекты вокруг. –

+0

Замечательные ответы от всех. У меня было ощущение, что в моих конкретных примерах не было бы другого выбора.Я видел публикации на SO от людей, которые так категоричны относительно того, как они избегают параметров любой ценой. Я надеялся, что, может быть, один из них сможет рассказать о том, как они достигают такого подвига. Что касается RVO, я несколько не решаюсь использовать специфические функции компилятора, так как было бы трудно объяснить моим начальникам, почему мне нужно перепроектировать половину моего кода, потому что он больше не работает из-за недостающей функции в результате изменение/обновление компилятора. –

ответ

7

Я не уверен, почему вы пытаетесь избежать передачи ссылок здесь. Это в значительной степени эти ситуации, когда существует семантика передачи по ссылке.

Код

static void doWork(ExpensiveCopy& ec_out, int someParam); 

выглядит прекрасно для меня.

Если вы действительно хотите изменить его, то у вас есть несколько вариантов

  1. Move DoWork так что это член ExpensiveCopy (который вы говорите, вы не можете сделать, так это выход)
  2. верните (умный) указатель из doWork вместо копирования.(Которые вы не хотите делать, как вы хотите, чтобы держать вещи в стеке)
  3. полагаются на РВО (что уже отмечалось поддерживается почти всех современных компиляторов)
+0

Согласовано. Только одно, вариант 1, о котором вы упоминаете, недоступен, поскольку OP говорит, что 'ExpensiveCopy' не может быть изменен. – stakx

+0

@stakx, вы правы, я пропустил это. Зафиксируем это – Glen

+0

@stakx: Я знаю, что ОП упомянул, что он не может изменить класс. Но почему он не может подклассифицировать его и добавить туда метод doWork()? – slebetman

1

Если вы не собираетесь вниз по маршруту «все неизменно», что не слишком хорошо сочетается с C++. вы не можете легко избежать параметров. Стандартная библиотека C++ использует их, и для этого достаточно для меня достаточно.

+0

Я полностью не согласен с последним утверждением: к сожалению, стандартная библиотека C++ не близка к идеалу (просто с моей головы: std :: string члены безумие, слишком продуманные потоки и т. Д.) Я не хочу начинать holiwar, но я думаю, что это не лучшая идея, чтобы оправдать что-то из-за хорошо известного примера. –

+2

Конечно, у него есть бородавки, но в целом я нахожу его чрезвычайно мощным и легким в использовании - я только хочу, чтобы мой собственный код был также хорошо разработан. – 2010-01-26 16:58:36

0

Что касается вашего первого примера: return value optimization часто разрешает создание возвращаемого объекта непосредственно на месте вместо того, чтобы копировать объект. Все современные компиляторы делают это.

3

Каждый полезный компилятор (оптимизация возвращаемого значения) РВО, если оптимизации включены, таким образом, эффективно нижеперечисленного не приводит к копированию:

Expensive work() { 
    // ... no branched returns here 
    return Expensive(foo); 
} 

Expensive e = work(); 

В некоторых случаях компиляторы могут применяться NRVO, оптимизация имени возвращаемого значения, а также:

Expensive work() { 
    Expensive e; // named object 
    // ... no branched returns here 
    return e; // return named object 
} 

Это, однако, не совсем достоверно, работает только в более простых случаях и нуждается в проверке. Если вы не проверяете каждый случай, просто используйте out-parameters со ссылками во втором случае.

2

ИМО первое, что вы должны задать себе, является ли копирование ExpensiveCopy действительно настолько дорогостоящим. И чтобы ответить на это, вам обычно понадобится профилировщик. Если профайлер не говорит вам, что копирование действительно является узким местом, просто напишите код, который легче читать: ExpensiveCopy obj = doWork(param);.

Конечно, действительно есть случаи, когда объекты нельзя копировать по производительности или по другим причинам. Затем применяется Neil's answer.

0

На какой платформе вы работаете?

Причина, по которой я прошу, заключается в том, что многие люди предложили Оптимизацию возвращаемого значения, которая является очень удобной оптимизацией компилятора, присутствующей почти в каждом компиляторе. Кроме того, Microsoft и Intel реализуют то, что они называют Оптимизацией возвращаемого значения имен, что еще более удобно.

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

В Именованной оптимизации возвращаемого значения вы можете вернуть значение по его имени, и компилятор сделает то же самое. Преимущество NRVO заключается в том, что вы можете выполнять более сложные операции над созданным значением (например, вызвать функции на нем) перед его возвратом.

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

С точки зрения избежания копирования единственный реальный способ сделать это с помощью указателей или ссылок, поскольку ваша функция должна изменять данные в том месте, в котором вы хотите, чтобы оно оказалось. Это означает, что вы, вероятно, хотите иметь параметр pass-by-reference.

Также я полагаю, что я должен указать, что пересылка по ссылке очень распространена в высокопроизводительном коде для этой причины. Копирование данных может быть невероятно дорогостоящим, и часто при просмотре кода часто возникает проблема.

2

В дополнении ко всем комментариям здесь я упомяну, что в C++ 0x вы редко используете выходной параметр для оптимизации назначения - из-Move Конструкторов (см here)

+0

Недавно я попытался настроить систему для поддержки чтения файлов, которая использовала семантику перемещения один раз. Отлично работает, но в итоге я сгорел из-за несовместимости с разными компиляторами. Пришлось изменить все это на параметры, которые были настоящим обломком. –

0

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

Простым фактом является то, что некоторые функции должны возвращать несколько вещей, и на большинстве языков это предлагает параметры. Common Lisp имеет multiple-value-bind и multiple-value-return, в котором список символов предоставляется связующим и возвращается список значений. В некоторых случаях функция может возвращать составное значение, такое как список значений, которые затем будут деконструированы, и для функции C++ не стоит возвращать std::pair. Возвращение более двух значений таким образом в C++ становится неудобным. Всегда можно определить структуру, но ее определение и создание часто будут более беспорядочными, чем параметры.

В некоторых случаях возвращаемое значение перегружается. В C getchar() возвращает int, при этом идея состоит в том, что существует больше значений int, чем char (true во всех реализациях, о которых я знаю, false в некоторых, о которых я легко представляю), поэтому одно из значений можно использовать для обозначения end- из-файла. atoi() возвращает целое число, либо целое число, представленное переданной им строкой, либо ноль, если его нет, поэтому он возвращает то же самое для «0» и «лягушка». (Если вы хотите узнать, было ли значение int или нет, используйте strtol(), у которого есть параметр out.)

Всегда существует метод выброса исключения в случае ошибки, но не все множественные возвращаемые значения являются ошибки, и не все ошибки являются исключительными.

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

0

Спросите себя, почему у вас есть какой-то метод, который выполняет работу над этим дорогостоящим копированием объекта в первую очередь. Скажем, у вас есть дерево, вы можете отправить дерево в какой-то метод строительства или дать дереву собственный метод строительства? Ситуации, подобные этому, возникают постоянно, когда у вас немного нет дизайна, но, как правило, складываются в себя, когда вы его поглаживаете.

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

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

Смежные вопросы