Должен ли я писать конструкторы с использованием rvalues для std::string?
У меня есть простой класс:
class X
{
std::string S;
X (const std::string& s) : S(s) { }
};
Я читал немного о rvalues в последнее время, и мне было интересно, если я должен написать конструктор для X
, используя rvalue, поэтому я мог бы обнаружить временные объекты типа std::string
?
Я думаю, что это должно выглядеть примерно так:
X (std::string&& s) : S(s) { }
Насколько мне известно, реализация std::string в компиляторах, поддерживающих С++ 11, должна использовать его при перемещении конструктора.
Ответы
Ответ 1
X (std::string&& s) : S(s) { }
Это не конструктор, принимающий rvalue, а конструктор, принимающий rvalue-reference. В этом случае вы не должны брать ссылки rvalue. Скорее передайте значение и затем перейдите в элемент:
X (std::string s) : S(std::move(s)) { }
Правило большого пальца состоит в том, что если вам нужно скопировать, сделайте это в интерфейсе.
Ответ 2
В интересах уточнения: ответы на поправку не ошибаются. Но ни одно из ваших первых предположений о добавлении перегрузки string&&
, кроме одной детали:
Добавить
X (std::string&& s) : S(std::move(s)) { }
т.е. вам все равно нужен move
, потому что хотя s
имеет объявленный тип ссылки rvalue на string
, выражение s
, используемое для инициализации s
, представляет собой выражение lvalue типа string
.
В самом деле, решение, которое вы впервые предложили (с добавленным движением), немного быстрее, чем решение с переходом по значению. Но оба правильны. Решение pass-by-value вызывает конструктор перемещения строки в дополнительное время при передаче аргументов lvalue и xvalue в конструктор X.
В случае аргументов lvalue копия выполняется в любом случае, и конструктор строковой копии, вероятно, будет намного дороже, чем конструктор перемещения строки (за исключением строк, которые вписываются в буфер коротких строк, и в этом случае перемещение и копирование примерно с той же скоростью).
В случае аргументов xvalue (значение x является значением l, которое было передано в std::move
), для решения пошагового решения требуются две конструкции перемещения вместо одного. Таким образом, он в два раза дороже, чем проход по ссылочному решению rvalue. Но все еще очень быстро.
Пункт этой статьи состоит в том, чтобы прояснить: пропускная способность является приемлемым решением. Но это не единственное приемлемое решение. Перегрузка с помощью pass-by-rvalue-ref выполняется быстрее, но имеет тот недостаток, что количество перегрузок, требуемое для масштабирования, равно 2 ^ N по мере увеличения количества аргументов N.
Ответ 3
Нет, не стоит. Что вы должны сделать, это заменить ваш текущий конструктор следующим:
X (std::string s) :S(std::move(s)) {}
Теперь вы можете обрабатывать оба значения l, которые будут скопированы в параметр, а затем перенесены в строку вашего класса и значения r, которые будут перемещены дважды (ваш компилятор, надеюсь, может оптимизировать эту дополнительную работу).
По большей части (есть исключения, в которые я не буду входить), вы не должны писать функции, которые берут ссылки r-значения, за исключением конструкторов перемещения классов, которые вы пишете. Каждый раз, когда вам нужна ваша собственная копия значения, и это не относится к просто конструкторам, вы должны взять ее по значению и переместить ее туда, куда ей нужно идти. Вы позволяете собственному конструктору перемещения решить, следует ли копировать или перемещать значение в зависимости от того, получает ли он значение r или значение l. В конце были введены ссылки r-value, чтобы сделать нашу жизнь проще, а не сложнее.
Ответ 4
Так как вам нужна копия аргумента, возьмите параметр по значению. Затем переместите его в данные члена. Это конструктор std::string
, который отвечает за обнаружение того, является ли приведенный аргумент значением rvalue или lvalue, а не вами.
class X
{
std::string s_;
X(std::string s) : s_(std::move(s)) {}
};