Как «вернуть объект» в C ++? - PullRequest
151 голосов
/ 28 июля 2010

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

В 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;, что выглядит не очень хорошо.Я не хочу возвращать скопированное значение, потому что оно неэффективно (я думаю), поэтому здесь возникают вопросы

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

Ответы [ 7 ]

103 голосов
/ 28 июля 2010

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

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

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

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


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

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

41 голосов
/ 28 июля 2010

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

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

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

15 голосов
/ 28 июля 2010

Просто верните объект, подобный этому:

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

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

Thing(const Thing& aThing) {}

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

Обновление

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

11 голосов
/ 28 июля 2010

Вы пытались использовать умные указатели (если Thing действительно большой и тяжелый объект), например, 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 
}
8 голосов
/ 28 июля 2010

Один быстрый способ определить, вызывается ли конструктор копирования, - добавить запись в конструктор копирования вашего класса:

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

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

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

1 голос
/ 28 июля 2010

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

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

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

...