Подтвердить что ты не робот

Должен ли конструктор перемещения ссылаться на константу или не константу?

В нескольких местах я видел рекомендуемые подписи копировать и перемещать конструкторы, заданные как:

struct T
{
    T();
    T(const T& other);
    T(T&& other);
};

Если конструктор копирования принимает ссылку на константу, а конструктор перемещения принимает неконстантную ссылку rvalue.

Насколько я вижу, это мешает мне использовать семантику перемещения при возврате объектов const из функции, например, в следующем случае:

T generate_t()
{
    const T t;
    return t;
}

Тестирование этого с помощью VC11 Beta, конструктор копирования T вызывается, а не конструктор перемещения. Даже используя return std::move(t);, конструктор копирования все еще вызывается.

Я вижу, как это имеет смысл, поскольку T const, поэтому не следует привязываться к T&&. Использование const T&& в сигнатуре конструктора перемещений отлично работает и имеет смысл, но тогда у вас есть проблема, потому что other является константой, вы не можете исключить ее членов, если их нужно отключить - это будет только работать, когда все члены являются скалярами или имеют конструкторы перемещения с правильной сигнатурой.

Похоже, что единственный способ убедиться, что конструктор перемещения вызывается в общем случае, чтобы сделать T не-const в первую очередь, но мне не нравится делать это - объекты, содержащие константу, являются хорошей формой и Я бы не ожидал, что клиент T знает, что им нужно идти против этой формы, чтобы повысить производительность.

Итак, я думаю, мой вопрос двоякий; во-первых, если конструктор перемещения принимает ссылку на константу или не константу rvalue? И второе: я прав в этой линии рассуждений? Что я должен прекратить возвращать вещи, которые являются const?

4b9b3361

Ответ 1

Он должен быть ссылкой const rvalue.

Если объект помещается в постоянное запоминающее устройство, вы не можете украсть у него ресурсы, даже если его формальное время жизни заканчивается в ближайшее время. Объектам, созданным как const в С++, разрешено жить в постоянной памяти (используя const_cast, чтобы попытаться изменить их результаты в undefined).

Ответ 2

Конструктор перемещения обычно должен принимать неконстантную ссылку.

Если бы можно было перемещаться из объекта const, это обычно означало бы, что было бы так же эффективно копировать объект, как "переходить" с него. На данный момент, как правило, нет никакой выгоды от наличия конструктора перемещения.

Вы также правы, что если у вас есть переменная, с которой вы потенциально захотите перейти с этого момента, она будет неконсольной.

Как я понимаю, именно по этой причине Скотт Мейерс изменил свой совет по возвращению объектов типа класса по значению из функций для С++ 11. Возвращаемые объекты по const-значению предотвращают непреднамеренную модификацию временного объекта, но также препятствуют перемещению с возвращаемого значения.

Ответ 3

Должен ли конструктор перемещения принимать константную или неконстантную ссылку на значение?

Это должна быть неконстантная ссылка. Ссылки на rvalue, во-первых, не имеют смысла в их постоянных формах просто потому, что вы хотите изменить их (в некотором смысле, вы хотите "переместить" их, вы хотите, чтобы их внутренние компоненты были для вас самих).

Кроме того, они были разработаны для использования без const, и я считаю, что единственное использование для ссылки на const rvalue - это нечто очень очень загадочное, о чем Скотт Мейерс упомянул в этом выступлении (со времени 42:20 до 44:47).

Я прав в этой линии рассуждений? Что я должен прекратить возвращать вещи, которые являются постоянными?

Я считаю, что это слишком общий вопрос, чтобы ответить на него. В этом контексте, я думаю, что стоит упомянуть, что есть функциональность std::forward, которая сохранит как rvalue-ness и lvalue-ness, так и const-ness, а также позволит избежать создания временной функции, которую будет делать обычная функция, если вы вернетесь что-нибудь передано этому.

Это возвращение также приведет к тому, что ссылка на rvalue будет "искажена" в ссылку на lvalue, и вы, как правило, не хотите, чтобы, следовательно, совершенная пересылка с вышеупомянутой функциональностью решала проблему.

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

Ответ 4

В дополнение к тому, что сказано в других ответах, иногда есть причины для конструктора перемещения или функции принять const T&&. Например, если вы передадите результат функции, которая возвращает const объект по значению в конструктор, вместо T(T&&) будет вызван T(const T&), как и следовало ожидать (см. Функцию g ниже).

Это причина удаления перегрузок, которые принимают const T&& для std::ref и std::cref вместо тех, которые принимают T&&.

В частности, порядок предпочтения при разрешении перегрузки следующий:

struct s {};

void f (      s&);  // #1
void f (const s&);  // #2
void f (      s&&); // #3
void f (const s&&); // #4

const s g ();
s x;
const s cx;

f (s ()); // rvalue        #3, #4, #2
f (g ()); // const rvalue  #4, #2
f (x);    // lvalue        #1, #2
f (cx);   // const lvalue  #2

Смотрите эту статью для более подробной информации.