Должен ли я писать конструкторы с использованием 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)) {}
};