Передать по значению или по ссылке в конструктор C ++, который должен хранить копию? - PullRequest
5 голосов
/ 04 декабря 2009

Должен ли конструктор значений C ++ (неявный или явный) принимать свои параметры по значению или по ссылке на const, когда ему необходимо сохранить копию аргумента (ов) в своем объекте в любом случае?

Вот самый короткий пример, который я могу себе представить:

struct foo {
    bar _b;
    foo(bar [const&] b) // pass by value or reference-to-const?
        : _b(b) { }
};

Идея заключается в том, что я хочу минимизировать вызовы конструктора копирования bar при создании объекта foo любым из возможных способов создания объекта foo.

Обратите внимание, что я немного знаю об исключении копирования и (именованной) оптимизации возвращаемого значения, и я прочитал «Хотите скорость? Передайте по значению» , однако я не думаю, что статья непосредственно обращается к этому варианту использования.

Редактировать: Я должен быть более конкретным.

Предположим, что я не могу знать sizeof(bar), или является ли bar фундаментальным, встроенным типом (bar может быть параметром шаблона, а foo может быть шаблоном класса класса). Кроме того, не думайте, что конструктор foo может быть встроенным (или, если уж на то пошло, bar). Предположим, что я, по крайней мере, могу использовать компилятор, который реализует RVO.

Я хотел бы, чтобы была возможность (учитывая оптимизацию компилятора), что такой вызов не будет вызывать никаких вызовов к конструктору копирования bar вообще (даже при выполнении _b(b) в foo) список инициализации):

foo f = function_that_creates_and_returns_a_bar_object_using_rvo();

Существует ли какая-либо возможность (учитывая стандарт C ++ 98), что это можно сделать, и если да, то более или менее вероятно, что она будет работать, если foo примет свой параметр ссылкой на const, а не на значение?

Ответы [ 8 ]

5 голосов
/ 04 декабря 2009

При прочих равных условиях я передаю const & для достаточно сложных классов и значение для POD и простых объектов.

Определение плюсов / минусов для передачи по константной ссылке вместо традиционного прохода по значению

Положительные:

  • Избегает копирования (большой плюс для объектов с дорогой копией)
  • Доступ только для чтения

Отрицательные:

  • Кто-то может использовать const, если он действительно хочет

Что еще важнее с позитивами, вы явно контролируете, когда происходит копирование (в вашем случае при прохождении инициализации _b в списке инициализаторов). Думая о негативах ... Я согласен, что это риск. Я думаю, что почти все хорошие программисты будут чувствовать себя грязно из-за использования const_cast. Кроме того, вы можете покорно искать const_cast и бросать помидоры в человека, бросающего const от аргумента. Но эй, вы никогда не знаете, и у кого есть время, чтобы посмотреть код, как ястреб:)?

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

4 голосов
/ 04 декабря 2009

Смотрите этот вопрос .

Используйте const T & arg, если sizeof(T)>sizeof(void*), и T arg, если sizeof(T) <= sizeof(void*). Все фундаментальные типы должны быть исключением из этого правила

2 голосов
/ 04 декабря 2009

В C ++ 98 и C ++ 03 вы должны передать const& bar и затем скопировать. В C ++ 0x вы должны передать bar, а затем выполнить перемещение (при условии, что bar имеет конструктор перемещения).

#include <utility>

struct foo
{
    bar _b;

    foo(bar b) : _b(std::move(b)) {}
};

Если вы создаете foo с параметром lvalue, вызывается конструктор копирования для создания копии b, и эта копия будет перемещена в _b. Если вы создадите foo с параметром rvalue, будет вызван конструктор перемещения bar для перемещения в b, а затем он снова будет перемещен в _b.

2 голосов
/ 04 декабря 2009

Я не проверял, что говорит стандарт, но, попробовав это эмпирически, я могу сказать вам, что GCC не оптимизирует копию, независимо от того, принимает ли конструктор аргумент по значению или по константной ссылке.

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

Подведем итог:

Bar b = makeBar();         // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar();   // Copy constructor of Bar called once
FooByValue f3(b);          // Copy constructor of Bar called twice
FooByRef f4(b);            // Copy constructor of Bar called only once

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

2 голосов
/ 04 декабря 2009

Стилистически, я бы сказал, что лучше передать по ссылке.

Если представление действительно имеет значение, то не догадайтесь. Измерь это.

1 голос
/ 04 декабря 2009

Я предполагаю, что bar имеет нормальный конструктор копирования вида bar(const bar &b)

Возьмите константную ссылку здесь. Конструктор bar возьмет эту ссылку и выполнит копирование. Всего копий: 1.

Если вы оставили ссылку отключенной, компилятор сделает копию b, передаст копию конструктору foo, который затем передаст ее bar и скопирует ее.

0 голосов
/ 04 декабря 2009

Честно говоря, в этом случае лучшим вариантом будет сделать ваш конструктор inline, при условии, что конструктор bar не выдает. Тогда просто перейдите по ссылке.

Также, как вы и подозревали, ваша статья здесь не применима.

0 голосов
/ 04 декабря 2009

Удерживайте shared_pointer для бара в вашем классе и передайте его как таковой. Таким образом, вы никогда не вызовете конструктор копирования:).

...