C++: возврат ссылкой и конструкторами копии

Ссылки в C++ экранируют меня.:)

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

Так, в целом, вот опции для того, чтобы сделать это, что я нашел:

  • Функциональный тип возврата может быть любой самим классом (MyClass fun() { ... }) или ссылка на класс (MyClass& fun() { ... }).
  • Функция может или создать переменную в строке возврата (return MyClass(a,b,c);) или возвратите существующую переменную (MyClass x(a,b,c); return x;).
  • Код, который получает переменную, может также иметь переменную любого типа: (MyClass x = fun(); или MyClass& x = fun();)
  • Код, который получает переменную, может или создать новую переменную на лету (MyClass x = fun();) или присвойте его существующей переменной (MyClass x; x = fun();)

И некоторые мысли об этом:

  • Это, кажется, плохая идея иметь тип возврата MyClass& потому что это всегда приводит к переменной, уничтожаемой, прежде чем она будет возвращена.
  • Конструктор копии только, кажется, принимает участие, когда я возвращаю существующую переменную. Когда возврат переменной создал в строке возврата, это никогда не называют.
  • Когда я присваиваю результат существующей переменной, деструктор также всегда умирает, прежде чем значение возвращено. Кроме того, никакой конструктор копии не становится вызванным, все же целевая переменная действительно получает членские значения объекта, возвращенного из функции.

Эти результаты так непоследовательны, что я чувствую себя полностью смущенным. Так, что ТОЧНО происходит здесь? Как я должен правильно создать и возвратить объект из функции?

19
задан James McNellis 12 October 2010 в 00:54
поделиться

9 ответов

Лучший способ понять копирование в C ++ - часто НЕ пытаться создать искусственный пример и обработать его - компилятору разрешено как удалять, так и добавлять копию вызовы конструктора, более или менее, как он считает нужным.

Итог - если вам нужно вернуть значение, верните значение и не беспокойтесь о каких-либо «расходах».

16
ответ дан 30 November 2019 в 02:41
поделиться

После некоторых исследований (см., например, здесь , здесь и здесь ), что < f: convertNumber > является проблемой. Кажется, что число, в которое он преобразует, зависит от вводимого значения - это может быть целое число или число с плавающей точки. Другими словами, он не смотрит на целевой тип - он просто генерирует экземпляр java.lang.Number. Что вряд ли идеально, хотя я не могу определить, потому что где-то я использую старую старую версию JSF или EL или что-то подобное.

Существует три решения:

  1. Использовать java.lang.Number в качестве типа объекта значения;
  2. Написать собственный конвертер;
  3. Не использовать < f: convertNumber > .

К сожалению, # 1 не является для меня вариантом по другим причинам, и я не хочу писать свой собственный конвертер в данный момент. Однако если изменить код, чтобы удалить convertNumber, все работает нормально. (Я также обновил тип объекта значения на Double , что было предложено в одной из ссылок, которые я посмотрел).

Это предотвращает исключения, и похоже, что JSF все еще делает то, что я хочу. Раздражает то, что невозможно указать convertNumber и тип преобразования в одном экземпляре.

-121--4460184-

Производительность оператора IF EXISTS

IF EXISTS(SELECT 1 FROM mytable WHERE someColumn = someValue)

зависит от имеющихся индексов, удовлетворяющих запросу.

-121--1292552-

прочитайте о RVO и NRVO (одним словом, эти два параметра означают Return Value Optimization и Named RVO и представляют собой методы оптимизации, используемые компилятором для выполнения того, чего вы пытаетесь достичь)

здесь вы найдете множество тем на stackoverflow

9
ответ дан 30 November 2019 в 02:41
поделиться

Присвойте его локальной переменной в области функций перед функцией итерации. Затем вы можете сослаться на него в:

OperationSelector = function(selectElement) { 
    this.selectElement = selectElement; 
} 

OperationSelector.prototype.populateSelectWithData = function(xmlData) { 
    var os = this;
    $(xmlData).find('operation').each(function() { 
        var operation = $(this); 
        os.selectElement.append(new Option(operation.attr("title")));
    }); 
}
-121--3632088-

Если ваша таблица является таблицей SQL И ваша база данных является PostgreSql, вы можете использовать PostgreSQL типы и функции .

Ну, это много условий...

-121--3196923-

При создании объекта, подобного

MyClass foo(a, b, c);

, он будет находиться в стеке в кадре функции. Когда эта функция заканчивается, ее кадр выскакивает из стека, и все объекты в этом кадре разрушаются. Избежать этого невозможно.

Таким образом, если вы хотите вернуть объект вызывающему абоненту, вы можете использовать только следующие параметры:

  • Return by value - требуется конструктор копирования (но вызов конструктора копирования может быть оптимизирован).
  • Верните указатель и убедитесь, что вы используете смарт-указатели, чтобы справиться с ним, или тщательно удалите его самостоятельно, когда закончите с ним.

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

4
ответ дан 30 November 2019 в 02:41
поделиться

Рекомендуемое чтение: Effective C++ by Scott Meyers. Там вы найдете очень хорошее объяснение этой темы (и многих других).

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

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

Канонический способ создания объекта в функции и его возврата - по значению, например:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

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

Можно вернуть по ссылке объект, созданный по new (т.е. на куче) - этот объект не будет уничтожен при возврате из функции. Однако позже вам придется уничтожить его где-то явно, вызвав delete.

Технически возможно также хранить объект, возвращаемый по значению, в ссылке, например:

MyClass& x = fun();

Однако, AFAIK, в этом нет особого смысла. Особенно потому, что можно легко передать эту ссылку другим частям программы, находящимся вне текущей области видимости; однако объект, на который ссылается x, является локальным объектом, который будет уничтожен, как только вы покинете текущую область видимости. Поэтому такой стиль может привести к неприятным ошибкам.

14
ответ дан 30 November 2019 в 02:41
поделиться

В принципе, возврат ссылки имеет смысл только в том случае, если объект все еще существует после выхода из метода. Компилятор предупредит вас, если вы вернете ссылку на что-то, что уничтожается.

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

Ссылки безопаснее указателей, потому что у них разная симантика, но за кулисами они являются указателями.

2
ответ дан 30 November 2019 в 02:41
поделиться

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

Стандарт допускает «исключение копирования», что означает, что конструктор копирования не нужно вызывать, когда вы возвращаете объект. Это бывает двух форм: оптимизация возвращаемого значения имени (NRVO) и оптимизация анонимного возвращаемого значения (обычно просто RVO).

Судя по тому, что вы говорите, ваш компилятор реализует RVO, но не NRVO - что означает, что это , вероятно, несколько более старый компилятор. Большинство современных компиляторов реализуют оба. Несоответствующий dtor в этом случае означает, что это, вероятно, что-то вроде gcc 3.4 или около того - хотя я точно не помню версию, в то время была такая, в которой была такая ошибка. Конечно, также возможно, что ваша аппаратура не совсем правильная, поэтому используется ctor, который вы не использовали, и для этого объекта вызывается соответствующий dtor.

В конце концов, вы застряли на одном простом факте: если вам нужно вернуть объект, вам нужно вернуть объект. В частности, ссылка может предоставить доступ только к (возможно, измененной версии) существующего объекта, но этот объект также должен был быть создан в какой-то момент. Если вы можете изменить какой-либо существующий объект, не вызывая проблем, это нормально, продолжайте и делайте это. Если вам нужен новый объект, отличный от уже имеющихся, сделайте это - предварительное создание объекта и передача ссылки на него может ускорить сам возврат, но не сэкономит времени в целом. Стоимость создания объекта примерно одинакова вне зависимости от того, выполняется оно внутри функции или вне ее. Любой достаточно современный компилятор будет включать RVO, поэтому вы не будете платить никаких дополнительных затрат за его создание в функции, а затем ее возврат - компилятор просто автоматизирует выделение пространства для объекта, куда он будет возвращен, и будет иметь функцию создайте его «на месте», где он будет доступен после возврата из функции.

3
ответ дан 30 November 2019 в 02:41
поделиться

Посмотрите на Трикл . Кроме того, при использовании маршрутизатора микропрограммное обеспечение маршрутизатора коммерческого класса обычно имеет некоторые службы формирования трафика QoS .

-121--3199756-

Вы заперты:

1) возвращаете указатель

MyClass * func () { //некоторые заглушки вернуть новый MyClass (a, b, c); }

2) возвращает копию объекта MyClass func () { возврат MyClass (a, b, c); }

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

-121--2185307-

Одним из возможных решений, в зависимости от варианта использования, является построение объекта по умолчанию вне функции, использование в ссылки на него и инициализация объекта, на который имеется ссылка в функции, например:

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

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

Но тогда подумайте, почему вы пытаетесь избежать копирования-строительства в первую очередь. Действительно ли «расходы» на создание копий влияют на вашу программу, или это случай преждевременной оптимизации?

1
ответ дан 30 November 2019 в 02:41
поделиться

Вы застряли в одном из следующих вариантов:

1) возврат указателя

MyClass * func () { // какой-то хлам return new MyClass (a, b, c); }

2) возврат копии объекта MyClass func () { return MyClass (a, b, c); }

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

0
ответ дан 30 November 2019 в 02:41
поделиться

Это не прямой ответ, но жизнеспособное предложение: Вы также можете возвращать указатель, обернутый в auto_ptr или smart_ptr. Тогда вы будете контролировать, какие конструкторы и деструкторы будут вызываться и когда.

0
ответ дан 30 November 2019 в 02:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: