Как "вернуть объект" в С++?

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

В Java я всегда могу возвращать ссылки на "локальные" объекты

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

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

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

Ответ 1

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

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

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

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


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

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

Ответ 2

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

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

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

Ответ 3

Вы пытались использовать интеллектуальные указатели (если 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 
}

Ответ 4

Просто верните объект следующим образом:

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

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

Thing(const Thing& aThing) {}

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

Обновление

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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