Я новичок в C ++, но не новичок в программировании. Я пытаюсь изучить C ++ (c ++ 11), и мне непонятно самое главное: передача параметров.
Я рассмотрел эти простые примеры:
Класс, в котором все члены примитивные типы:
CreditCard(std::string number, int expMonth, int expYear,int pin):number(number), expMonth(expMonth), expYear(expYear), pin(pin)
Класс, имеющий в качестве членов примитивные типы + 1 сложный тип:
Account(std::string number, float amount, CreditCard creditCard) : number(number), amount(amount), creditCard(creditCard)
Класс, имеющий в качестве членов примитивные типы + 1 коллекцию некоторого сложного типа:
Client(std::string firstName, std::string lastName, std::vector<Account> accounts):firstName(firstName), lastName(lastName), accounts(accounts)
Когда я создаю учетную запись, я делаю следующее:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc);
Очевидно, что в этом сценарии кредитная карта будет скопирована дважды. Если я перепишу этот конструктор как
Account(std::string number, float amount, CreditCard& creditCard)
: number(number)
, amount(amount)
, creditCard(creditCard)
будет один экземпляр. Если я перепишу его как
Account(std::string number, float amount, CreditCard&& creditCard)
: number(number)
, amount(amount)
, creditCard(std::forward<CreditCard>(creditCard))
Будет 2 хода и ни одной копии.
Я думаю, что иногда вы можете захотеть скопировать какой-то параметр, иногда вы не хотите копировать при создании этого объекта.
Я пришел с C #, и, поскольку я привык к ссылкам, это немного странно для меня, и я думаю, что для каждого параметра должно быть две перегрузки, но я знаю, что ошибаюсь.
Есть ли какие-либо передовые методы отправки параметров на C ++, потому что я действительно считаю это, скажем так, нетривиальным. Как бы вы поступили с моими приведенными выше примерами?
std::string
это класс, аCreditCard
не примитивный тип."abc"
имеет не типstd::string
, не типchar */const char *
, а типconst char[N]
(в данном случае N = 4 из-за трех символов и нуля). Это хорошее, распространенное заблуждение, чтобы не мешать.Ответы:
САМЫЙ ВАЖНЫЙ ВОПРОС ПЕРВЫЙ:
Если вашей функции необходимо изменить исходный передаваемый объект, чтобы после возврата вызова изменения этого объекта были видны вызывающему, тогда вы должны передать ссылку lvalue :
Если вашей функции не нужно изменять исходный объект и не нужно создавать его копию (другими словами, ей нужно только наблюдать за его состоянием), тогда вы должны передать ссылку lvalue на
const
:Это позволит вам вызывать функцию как с lvalues (lvalues - это объекты со стабильной идентичностью), так и с rvalues (rvalue - это, например, временные объекты или объекты, от которых вы собираетесь перейти в результате вызова
std::move()
).Можно также утверждать, что для основных типов или видов , для которых копирование быстро , например
int
,bool
илиchar
, нет необходимости передавать по ссылке , если функции просто необходимо соблюдать значение, и передачи по значению следует отдавать предпочтение . Это правильно, если ссылочная семантика не требуется, но что, если функция хочет где-то сохранить указатель на тот же самый входной объект, чтобы при дальнейшем чтении этого указателя были видны изменения значений, которые были выполнены в какой-то другой части код? В этом случае передача по ссылке - правильное решение.Если ваша функция не требуется изменять исходный объект, но необходимо сохранить копию этого объекта ( возможно, чтобы вернуть результат преобразования ввода без изменения ввода ), тогда вы можете рассмотреть возможность выбора по значению :
Вызов вышеуказанной функции всегда будет приводить к одной копии при передаче lvalues и к одной копии при передаче rvalue. Если функция должна хранить этот объект где - то, вы могли бы выполнить дополнительный шаг от него (например, в случае ,
foo()
является функцией - членом , который должен хранить значение в элементе данных ).Если ходы дорогие для объектов типа
my_class
, вы можете рассмотреть вопрос о перегрузкеfoo()
и предоставить одну версию для lvalue (принимая ссылку lvalue наconst
) и одну версию для rvalue (принимая ссылку rvalue):Фактически, указанные выше функции настолько похожи, что вы можете сделать из них одну-единственную функцию:
foo()
может стать шаблоном функции и вы могли бы использовать идеальную пересылку для определения того, будет ли внутреннее сгенерировано перемещение или копия переданного объекта:Вы можете узнать больше об этом дизайне, посмотрев доклад Скотта Мейерса (помните, что термин « универсальные ссылки », который он использует, нестандартен).
Следует иметь в виду, что
std::forward
обычно это заканчивается перемещением для rvalues, поэтому, даже если это выглядит относительно невинно, пересылка одного и того же объекта несколько раз может быть источником проблем - например, перемещение от одного и того же объекта дважды! Так что будьте осторожны, чтобы не поместить это в цикл и не пересылать один и тот же аргумент несколько раз в вызове функции:Также обратите внимание, что вы обычно не прибегаете к решению на основе шаблонов, если у вас нет для этого веской причины, поскольку это затрудняет чтение вашего кода. Обычно вам следует сосредоточиться на ясности и простоте .
Вышеупомянутое - всего лишь простые рекомендации, но в большинстве случаев они укажут вам на хорошие дизайнерские решения.
ОТНОСИТЕЛЬНО ОТДЫХА ВАШЕГО ПОЧТА:
Это не так. Начнем с того, что ссылка rvalue не может быть привязана к lvalue, поэтому она будет компилироваться только тогда, когда вы передаете rvalue типа
CreditCard
вашему конструктору. Например:Но это не сработает, если вы попытаетесь это сделать:
Поскольку
cc
это lvalue, а ссылки rvalue не могут связываться с lvalue. Более того, при привязке ссылки к объекту перемещение не выполняется : это просто привязка ссылки. Таким образом, ход будет только один .Итак, основываясь на рекомендациях, приведенных в первой части этого ответа, если вас беспокоит количество генерируемых ходов, когда вы берете
CreditCard
значение по значению, вы можете определить две перегрузки конструктора, одна из которых принимает ссылку lvalue наconst
(CreditCard const&
), а другая - ссылка rvalue (CreditCard&&
).Разрешение перегрузки выберет первое при передаче lvalue (в этом случае будет выполнено одно копирование) и второе при передаче rvalue (в этом случае будет выполнено одно перемещение).
Ваше использование
std::forward<>
обычно наблюдается, когда вы хотите добиться идеальной пересылки . В этом случае ваш конструктор будет фактически шаблоном конструктора и будет выглядеть примерно так:В некотором смысле, это объединяет обе перегрузки, которые я показал ранее, в одну единственную функцию:
C
будет выведено,CreditCard&
что это произойдет в случае, если вы передаете lvalue, и из-за правил свертывания ссылок это приведет к созданию этой функции:Это приведет к копии-конструкцию из
creditCard
, как вы хотите. С другой стороны, когда будет передано rvalue,C
будет выведено, что естьCreditCard
, и вместо этого будет создан экземпляр этой функции:Это вызовет перемещение-конструкцию из
creditCard
, которая является то , что вы хотите (потому что значение передается является Rvalue, и это означает , что мы имеем право двигаться от него).источник
const
. Если вам нужно изменить исходный объект, возьмите по ссылке non-`const. Если вам нужно сделать копию, а ходы дешевы, берите по стоимости, а затем двигайтесь.obj
вместо того, чтобы делать локальную копию для перемещения, не так ли?std::forward
может быть вызван только один раз . Я видел, как люди помещали его в циклы и т.д., и поскольку этот ответ увидят многие новички, ИМХО должен быть толстый ярлык «Предупреждение!», Чтобы помочь им избежать этой ловушки.std::is_constructible<>
игнорировать черту типа, если они не являются должным образом SFINAE- ограниченный - что может быть нетривиальным для некоторых.Во-первых, позвольте мне исправить некоторые детали. Когда вы говорите следующее:
Это неправда. Привязка к ссылке rvalue - это не ход. Есть только один ход.
Кроме того, поскольку
CreditCard
это не параметр шаблона,std::forward<CreditCard>(creditCard)
это просто многословный способ сказатьstd::move(creditCard)
.Сейчас...
Если у ваших типов есть «дешевые» ходы, вы можете просто упростить себе жизнь и брать все по достоинству и «с собой
std::move
».Такой подход даст вам два хода, тогда как он может дать только один, но если ходы дешевые, они могут быть приемлемыми.
Пока мы занимаемся этим вопросом «дешевых ходов», я должен напомнить вам, что
std::string
он часто реализуется с помощью так называемой оптимизации малых строк, поэтому ее ходы могут быть не такими дешевыми, как копирование некоторых указателей. Как обычно с проблемами оптимизации, важно это или нет, спрашивать профилировщика, а не меня.Что делать, если вы не хотите делать лишние ходы? Может быть, они окажутся слишком дорогими, или, что еще хуже, возможно, что типы невозможно переместить, и вы можете получить дополнительные копии.
Если есть только один проблемный параметр, вы можете предоставить две перегрузки с помощью
T const&
иT&&
. Это будет связывать ссылки все время до фактической инициализации члена, когда происходит копирование или перемещение.Однако, если у вас есть более одного параметра, это приводит к экспоненциальному взрыву числа перегрузок.
Это проблема, которую можно решить с помощью безупречной пересылки. Это означает, что вместо этого вы пишете шаблон и используете его
std::forward
для переноса категории значений аргументов в их конечный пункт назначения в качестве членов.источник
Account("",0,{brace, initialisation})
.Во-первых,
std::string
это довольно здоровенный тип классаstd::vector
. Это уж точно не примитивно.Если вы берете в конструктор какие-либо большие подвижные типы по значению, я бы добавил
std::move
их в член:Именно так я бы рекомендовал реализовать конструктор. Это приводит к тому, что элементы
number
иcreditCard
создаются, а не копируются. Когда вы используете этот конструктор, будет одна копия (или перемещение, если она временная), поскольку объект передается в конструктор, а затем одно перемещение при инициализации члена.Теперь рассмотрим этот конструктор:
Вы правы, здесь будет задействована одна копия
creditCard
, потому что она сначала передается в конструктор по ссылке. Но теперь вы не можете передаватьconst
объекты в конструктор (поскольку ссылка не являетсяconst
) и вы не можете передавать временные объекты. Например, вы не могли этого сделать:Теперь рассмотрим:
Здесь вы показали неправильное понимание ссылок на rvalue и
std::forward
. Вы должны действительно использовать толькоstd::forward
тогда, когда объект, который вы пересылаете, объявлен какT&&
для некоторого выведенного типаT
. ЗдесьCreditCard
не выводится (я предполагаю), поэтомуstd::forward
используется по ошибке. Найдите универсальные ссылки .источник
Я использую довольно простое правило для общего случая: используйте копию для POD (int, bool, double, ...) и const & для всего остального ...
И желаете копировать или нет, не отвечает сигнатура метода, а больше то, что вы делаете с параметрами.
точность указателя: я их почти не использовал. Единственное преимущество перед & в том, что они могут быть нулевыми или переназначаться.
источник
(*) указатели могут относиться к динамически выделяемой памяти, поэтому, когда это возможно, вам следует предпочесть ссылки указателям, даже если ссылки, в конце концов, обычно реализуются как указатели.
(**) «обычно» означает конструктор копирования (если вы передаете объект того же типа параметра) или обычный конструктор (если вы передаете совместимый тип для класса). Когда вы передаете объект как
myMethod(std::string)
, например, конструктор копирования будет использоваться, если емуstd::string
передано, поэтому вы должны убедиться, что он существует.источник