2010-07-28 4 views
131

Я знаю, что название звучит знакомо, так как есть много похожих вопросов, но я прошу о другом аспекте проблемы (я знаю разницу между тем, что есть в стеке и помещением их в кучу).Как «вернуть объект» на C++?

В Java я всегда могу вернуться ссылки на «локальный» объекты

public Thing calculateThing() { 
    Thing thing = new Thing(); 
    // do calculations and modify thing 
    return thing; 
} 

В C++, чтобы сделать что-то подобное у меня есть 2 варианта

(1) Я могу использовать ссылки, когда мне нужно " возвращение»объект

void calculateThing(Thing& thing) { 
    // do calculations and modify thing 
} 

Затем использовать его как это

Thing thing; 
calculateThing(thing); 

(2) Или я могу вернуть указатель на динамически выделенный объект

Thing* calculateThing() { 
    Thing* thing(new Thing()); 
    // do calculations and modify thing 
    return thing; 
} 

Затем используйте его как этот

Thing* thing = calculateThing(); 
delete thing; 

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

  • Есть ли третье решение (которое не требует копирования значения)?
  • Есть ли проблемы, если я придерживаюсь первого решения?
  • Когда и почему следует использовать второе решение?
+17

+1 для красиво поставив вопрос. – Kangkan

+0

Чтобы быть очень педантичным, немного неточно сказать, что «функции возвращают что-то». Вернее, * вычисление вызова функции вызывает значение *. Значение всегда является объектом (если оно не является функцией void).Различие заключается в том, является ли значение значением glvalue или значением prvalue, которое определяется, является ли объявленный * возвращаемый тип * ссылкой или нет. –

ответ

94

Я не хочу, чтобы вернуть скопированное значение, потому что это неэффективно

Докажите это.

Посмотрите на RVO и NRVO, а также на C++ 0x move-semantics. В большинстве случаев в C++ 03 параметр out - это просто хороший способ сделать ваш код уродливым, а в C++ 0x на самом деле вы будете болеть сами, используя параметр out.

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

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

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

+0

Спасибо GMan, я просто изучаю C++, и я где-то читал, что при возврате значения значение нужно скопировать в другое место, а копирование выглядит неэффективным, не так ли? – phunehehe

+9

@phunehehe: Нет смысла спекулировать, вы должны прокомментировать свой код и узнать. (Подсказка: нет). Компиляторы очень умны, они не собираются тратить время на копирование вещей, если им это не нужно. * Даже если * копирование стоит чего-то, вы все равно должны стремиться к хорошему коду через быстрый код; Хороший код легко оптимизировать, когда скорость становится проблемой. Нет смысла угадывать код для чего-то, о чем вы не подозреваете, является проблемой; особенно если вы на самом деле замедляете это или ничего не получаете от этого. И если вы используете C++ 0x, move-semantics делает это не проблемой. – GManNickG

+0

@phunehehe это также может быть полезно в некоторых случаях, http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ – Anycorn

0

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

1

Во-первых, у вас есть код ошибки, вы имеете в виду Thing *thing(new Thing()); и только return thing;.

  • shared_ptr<Thing>. Deref это как это было указателем. Он будет удален для вас, когда последняя ссылка на Thing будет выходить за рамки.
  • Первое решение очень распространено в наивных библиотеках. Он имеет некоторую производительность и синтаксические накладные расходы, избегая его, если это возможно.
  • Используйте второе решение только в том случае, если вы можете гарантировать, что не будут исключены исключения или когда производительность будет абсолютно критической (вы будете взаимодействовать с C или сборкой, Соответствующий).
+0

спасибо Мэтт, я исправляю синтаксис – phunehehe

36

Просто создать объект и вернуть его

Thing calculateThing() { 
    Thing thing; 
    // do calculations and modify thing 
    return thing; 
} 

Я думаю, что вы будете делать себе пользу, если вы забыли об оптимизации и просто писать читаемый код (вам нужно запустить профайлер позже - но не предварительно оптимизировать).

+1

'Thing thing();' объявляет локальную функцию и возвращает 'Thing'. – dreamlax

+1

Вещь вещь() объявляет функцию, возвращающую вещь. В вашем теле функции нет объекта Thing. –

+2

@dream @Charles: Hivemind? :) – GManNickG

11

ли вы пытаетесь использовать смарт-указатели (если вещь действительно большой и тяжелый предмет), как auto_ptr:


std::auto_ptr<Thing> calculateThing() 
{ 
    std::auto_ptr<Thing> thing(new Thing); 
    // .. some calculations 
    return thing; 
} 


// ... 
{ 
    std::auto_ptr<Thing> thing = calculateThing(); 
    // working with thing 

    // auto_ptr frees thing 
} 
+2

'auto_ptr' устарели; вместо этого используйте 'shared_ptr' или' unique_ptr'. – MBraedley

6

один быстрый способ определить, если конструктор копирования вызывается, чтобы добавить регистрацию в свой конструктор копирования класса:

MyClass::MyClass(const MyClass &other) 
{ 
    std::cout << "Copy constructor was called" << std::endl; 
} 

MyClass someFunction() 
{ 
    MyClass dummy; 
    return dummy; 
} 

Вызов someFunction; число строк, которые вы получили, будет меняться от 0, 1 и 2. Если вы его не получили, ваш компилятор оптимизировал возвращаемое значение (что разрешено делать). Если вы получите не получите 0, а ваш конструктор копий смехотворно дорог, затем искать альтернативные способы возврата экземпляров из ваших функций.

10

Просто возвращает объект, как это:

Thing calculateThing() 
{ 
    Thing thing(); 
    // do calculations and modify thing 
    return thing; 
} 

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

Thing(const Thing& aThing) {} 

Это может быть немного медленнее, но это может быть не проблема.

Update

Компилятор, вероятно, оптимизировать вызов конструктора копирования, так что не будет никаких дополнительных накладных расходов. (Как отмечалось в комментарии).

+7

'Thing thing();' объявляет локальную функцию, возвращающую 'Thing', также стандарт позволяет компилятору опустить конструктор копирования в том случае, когда вы представили; любой современный компилятор, вероятно, сделает это. – dreamlax

+0

@dreamlax: Согласовано –

+0

Вы приносите хороший момент для реализации конструктора копирования, особенно если необходима глубокая копия. – mbadawi23

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