Передать по значению или ссылке конструктору C++, которому необходимо сохранить копию?

StackOverflow https://stackoverflow.com/questions/1847846

Вопрос

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

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

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 принимает свой параметр по ссылке на константу, а не по значению?

Это было полезно?

Решение

В 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.

Другие советы

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

Изложение плюсов и минусов передачи по константной ссылке вместо традиционной передачи по значению

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

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

Негативы:

  • Кто-то может использовать const для ссылки, если он действительно этого хочет.

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

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

Посмотри это вопрос.

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

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

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

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

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

Обобщить:

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top