Почему неконстантная ссылка не может привязываться к временному объекту?

226

Почему нельзя получить неконстантную ссылку на временный объект, который getx()возвращает функция ? Понятно, что это запрещено C ++ Standard, но меня интересует цель такого ограничения, а не ссылка на стандарт.

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. Ясно, что время жизни объекта не может быть причиной, потому что постоянная ссылка на объект не запрещена стандартом C ++.
  2. Понятно, что временный объект не является константой в приведенном выше примере, поскольку разрешены вызовы непостоянных функций. Например, ref()может изменить временный объект.
  3. Кроме того, ref()позволяет обмануть компилятор и получить ссылку на этот временный объект, и это решает нашу проблему.

К тому же:

Они говорят: «Присвоение временного объекта константной ссылке увеличивает время жизни этого объекта» и «Ничего не сказано о неконстантных ссылках, хотя». Мой дополнительный вопрос . Продлевает ли следующее назначение время жизни временного объекта?

X& x = getx().ref(); // OK
Алексей Малистов
источник
4
Я не согласен с частью «время жизни объекта не может быть причиной» только потому, что в стандарте указано, что назначение временного объекта для ссылки на const увеличивает время жизни этого объекта до времени жизни ссылки на const. Ничего не сказано о неконстантных ссылках, хотя ...
SadSido
3
Ну, в чем причина этого "Ничего не сказано о неконстантных ссылках, хотя ...". Это часть моего вопроса. Есть ли в этом смысл? Может быть, авторы Standard просто забыли о неконстантных ссылках, и скоро мы увидим следующий основной выпуск?
Алексей Малистов
2
ПОЛУЧИЛСЯ № 88: Кандидат на звание «Самый важный const». herbutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry
fnieto - Фернандо Ньето
4
@Michael: VC связывает значения с неконстантными ссылками. Они называют это функцией, но на самом деле это ошибка. (Обратите внимание, что это не ошибка, потому что она по своей сути нелогична, а потому, что она была исключена явно для предотвращения глупых ошибок.)
sbi

Ответы:

97

Из этой статьи блога Visual C ++ о ссылках rvalue :

... C ++ не хочет, чтобы вы случайно модифицировали временные переменные, но прямой вызов неконстантной функции-члена для модифицируемого значения r является явным, так что это разрешено ...

По сути, вы не должны пытаться модифицировать временные объекты по той самой причине, что они являются временными объектами и теперь умирают в любой момент. Причина, по которой вам разрешено вызывать неконстантные методы, заключается в том, что вы можете делать некоторые «глупые» вещи, если вы знаете, что делаете, и вы явно об этом (например, используете reinterpret_cast). Но если вы привязываете временную ссылку к неконстантной ссылке, вы можете продолжать передавать ее «навсегда», просто чтобы ваши манипуляции с объектом исчезли, потому что где-то по пути вы полностью забыли, что это было временным.

Если бы я был тобой, я бы переосмыслил дизайн своих функций. Почему g () принимает ссылку, изменяет ли она параметр? Если нет, сделайте это константной ссылкой, если да, почему вы пытаетесь передать ему временный текст, не волнует ли это, что вы изменяете временный код? Почему getx () все равно возвращается временно? Если вы поделитесь с нами своим реальным сценарием и тем, что вы пытаетесь достичь, вы можете получить несколько хороших советов о том, как это сделать.

Идти против языка и дурачить компилятор редко решает проблемы - обычно это создает проблемы.


Изменить: Отвечая на вопросы в комментарии: 1) X& x = getx().ref(); // OK when will x die?- Я не знаю, и мне все равно, потому что это именно то, что я имею в виду под "идти против языка". Язык говорит: «Временные символы умирают в конце утверждения, если они не связаны с константной ссылкой, и в этом случае они умирают, когда ссылка выходит из области видимости». Применяя это правило, кажется, что x уже мертв в начале следующего оператора, так как он не связан с константной ссылкой (компилятор не знает, что возвращает ref ()). Это только предположение однако.

2) Я четко сформулировал цель: вам не разрешено изменять временные данные, потому что это просто не имеет смысла (игнорирование ссылок на значения C ++ 0x). Вопрос "тогда почему я могу звонить неконстантным участникам?" хороший, но у меня нет лучшего ответа, чем тот, который я уже сказал выше.

3) Ну, если я прав насчет х в X& x = getx().ref();смерти в конце утверждения, проблемы очевидны.

В любом случае, основываясь на вашем вопросе и комментариях, я не думаю, что даже эти дополнительные ответы вас удовлетворит. Вот последняя попытка / краткое изложение: Комитет C ++ решил, что не имеет смысла изменять временные данные, поэтому они запретили привязку к неконстантным ссылкам. Может быть, некоторые реализации компилятора или исторические проблемы также были вовлечены, я не знаю. Затем возник какой-то конкретный случай, и было решено, что, несмотря ни на что, они по-прежнему допускают прямое изменение посредством вызова неконстантного метода. Но это исключение - вы, как правило, не можете изменять временные данные. Да, C ++ часто такой странный.

СБК
источник
2
@sbk: 1) На самом деле, правильная фраза: «... в конце полного выражения ...». Я полагаю, что «полное выражение» определяется как выражение, не являющееся подвыражением какого-либо другого выражения. Всегда ли это то же самое, что «конец утверждения», я не уверен.
ВОО
5
@sbk: 2) На самом деле, вы которые разрешается изменять rvalues (временных). Это запрещено для встроенных типов ( и intт.д.), но это допускается для определяемых пользователем типов: (std::string("A")+"B").append("C").
ВОО
6
@sbk: 3) Причина, по которой Страуструп (в D & E) запрещает привязку значений r к неконстантным ссылкам, заключается в том, что, если у Алексея g()будет изменен объект (что можно ожидать от функции, использующей неконстантную ссылку), это изменило бы объект, который собирается умереть, так что никто не мог бы получить измененное значение в любом случае. Он говорит, что это, скорее всего, ошибка.
ВОО
2
@sbk: Извините, если я вас обидел, но я не думаю, что 2) придирки. R-значения просто не являются константными, если вы не сделаете их такими, и вы можете изменить их, если они не являются встроенными. Мне потребовалось некоторое время, чтобы понять, например, что, например, на примере строки, я делаю что-то не так (JFTR: нет), поэтому я склонен воспринимать это различие серьезно.
ВОО
5
Я согласен с sbi - этот вопрос совсем не придирчив. Это все основа семантики перемещения, что значения типов классов лучше хранить неконстантными.
Йоханнес Шауб -
38

В ваш код getx()возвращается временный объект, так называемое «rvalue». Вы можете скопировать значения r в объекты (так называемые переменные) или связать их с константными ссылками (что продлит их время жизни до конца жизни ссылки). Вы не можете привязать значения к неконстантным ссылкам.

Это было преднамеренное проектное решение, чтобы предотвратить случайное изменение пользователями объекта, который погибнет в конце выражения:

g(getx()); // g() would modify an object without anyone being able to observe

Если вы хотите сделать это, вам сначала нужно будет сделать локальную копию или объект или связать его с константной ссылкой:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

Обратите внимание, что следующий стандарт C ++ будет включать ссылки на rvalue. Поэтому то, что вы знаете как ссылки, становится так называемым «ссылками lvalue». Вам будет разрешено связывать rvalue со ссылками на rvalue, и вы можете перегрузить функции на "rvalue-ness":

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

Идея, стоящая за ссылками на rvalue, заключается в том, что, поскольку эти объекты все равно умрут, вы можете воспользоваться этими знаниями и реализовать так называемую «семантику перемещения», определенный вид оптимизации:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
SBI
источник
2
Привет, это потрясающий ответ. Нужно знать одну вещь: g(getx())она не работает, потому что ее подпись есть g(X& x)и get(x)возвращает временный объект, поэтому мы не можем связать временный объект ( rvalue ) с непостоянной ссылкой, правильно? И в вашем первом фрагменте кода, я думаю, что это будет const X& x2 = getx();вместо const X& x1 = getx();..
SexyBeast
1
Спасибо за указание на эту ошибку в моем ответе через 5 лет после того, как я ее написал! :-/Да, ваши рассуждения верны, хотя и немного задом наперед: мы не можем привязать временные constссылки к ссылкам, не являющимся (lvalue), и поэтому временное значение, возвращаемое getx()(и не get(x)), не может быть привязано к ссылке lvalue, аргументом которой является g().
СБИ
Хм, что вы имели в виду getx()(и не get(x)) ?
SexyBeast
Когда я пишу «... getx () (а не get (x)) ...» , я имею в виду, что имя функции есть getx(), а не get(x)(как вы написали).
СБИ
Этот ответ смешивает терминологию. Rvalue является категорией выражения. Временный объект - это объект. Значение может обозначать или не обозначать временный объект; и временный объект может или не может быть обозначен как rvalue.
ММ
16

То, что вы показываете, это то, что оператор цепочки разрешен.

 X& x = getx().ref(); // OK

Выражение 'getx (). Ref ();' и это выполняется до завершения перед присваиванием 'x'.

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

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);

// or more commonly
std::cout << getManiplator() << 5;

Посмотрите на конец этого ответа для лучшего примера этого

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

Значение, возвращаемое ref (), является допустимой ссылкой, но метод не обращает никакого внимания на срок службы возвращаемого объекта (поскольку он не может иметь эту информацию в своем контексте). Вы в основном только что сделали эквивалент:

x& = const_cast<x&>(getX());

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

Таким образом, единственный оставшийся вопрос заключается в том, почему стандарт не хочет разрешать использование временных ссылок для продления срока службы объекта до конца утверждения?

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

Подумайте об этой ситуации:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.

Продление срока службы этого временного объекта будет очень запутанным.
Пока следующее:

int const& y = getI();

Даст вам код, который будет интуитивно понятен в использовании и понимании.

Если вы хотите изменить значение, вы должны возвращать значение в переменную. Если вы пытаетесь избежать затрат на копирование объекта обратно из функции (так как кажется, что объект является копией, созданной обратно (технически это так)). Тогда не беспокойтесь, компилятор очень хорош в «Оптимизации возвращаемого значения»

Мартин Йорк
источник
3
«Таким образом, единственный оставшийся вопрос заключается в том, почему стандарт не хочет разрешать использование временных ссылок для продления срока службы объекта до конца утверждения?» Вот и все! Вы понимаете мой вопрос. Но я не согласен с вашим мнением. Вы говорите «сделать компилятор очень сложным», но это было сделано для справки. В своем примере вы говорите: «Примечание x - это псевдоним переменной. Какую переменную вы обновляете». Нет проблем. Существует уникальная переменная (временная). Некоторый временный объект (равный 5) должен быть изменен.
Алексей Малистов
@Martin: свисающие ссылки не только неопрятны. Они могут привести к серьезным ошибкам при доступе позже в методе!
мммммммм
1
@Alexey: обратите внимание, что тот факт, что привязка его к константной ссылке увеличивает время жизни временного объекта, является исключением, которое было намеренно добавлено (TTBOMK для обеспечения ручной оптимизации). Не было добавлено исключение для неконстантных ссылок, потому что привязка временного к неконстантной ссылке, скорее всего, была ошибкой программиста.
СБИ
1
@alexy: ссылка на невидимую переменную! Не так интуитивно.
Мартин Йорк,
const_cast<x&>(getX());не имеет смысла
любопытный парень
10

Почему это обсуждается в C ++ FAQ ( жирный шрифт ):

В C ++ неконстантные ссылки могут связываться с lvalues, а константные ссылки могут связываться с lvalue или rvalue, но нет ничего, что может связываться с неконстантным rvalue. Это защищает людей от изменения временных ценностей, которые уничтожаются до того, как их новое значение может быть использовано . Например:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

Если бы это incr (0) было разрешено, либо какое-то временное значение, которое никто никогда не видел, было бы увеличено, или, что гораздо хуже, значение 0 стало бы 1. Последнее звучит глупо, но на самом деле была такая ошибка в ранних компиляторах Фортрана, которые устанавливали в сторону ячейки памяти для хранения значения 0.

Тони Делрой
источник
Было бы смешно видеть лицо программиста, которого укусила эта фортранская «нулевая ошибка»! x * 0 дает x? Какой? Какой??
Джон Д
Этот последний аргумент особенно слаб. Ни один компилятор, достойный упоминания, никогда бы не изменил значение с 0 на 1 и даже не интерпретировал бы incr(0);таким образом. Очевидно, что если бы это было разрешено, это было бы интерпретировано как создание временного целого числа и передача его пользователюincr()
user3204459
6

Основная проблема заключается в том, что

g(getx()); //error

является логической ошибкой: изменяет gрезультат, getx()но у вас нет шансов проверить измененный объект. Если gбы не нужно было изменять его параметр, тогда не потребовалась бы ссылка на lvalue, он мог бы взять параметр по значению или по константной ссылке.

const X& x = getx(); // OK

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

Однако это не возможно сделать

X& x = getx(); // error

допустимо, не делая g(getx())валидным, чего и пытались избежать дизайнеры языка.

g(getx().ref()); //OK

допустимо, потому что методы знают только о константе this, они не знают, вызваны ли они на lvalue или на rvalue.

Как всегда в C ++, у вас есть обходной путь для этого правила, но вы должны сообщить компилятору, что вы знаете, что делаете, будучи явным:

g(const_cast<x&>(getX()));
Дэн Бериндей
источник
5

Похоже, на первоначальный вопрос о том, почему это не разрешено, дан четкий ответ: «потому что это, скорее всего, ошибка».

FWIW, я думал, что покажу, как это можно сделать, хотя я не думаю, что это хорошая техника.

Причина, по которой я иногда хочу передать временное значение методу, принимающему неконстантную ссылку, заключается в том, что он намеренно отбрасывает значение, возвращаемое по ссылке, о которой вызывающий метод не заботится. Что-то вроде этого:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

Как объяснялось в предыдущих ответах, это не компилируется. Но это компилируется и работает правильно (с моим компилятором):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

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

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

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

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

Крис

Крис Пирсон
источник
4

Зачем тебе это X& x = getx();? Просто используйте X x = getx();и положитесь на RVO.

DevFred
источник
3
Потому что я хочу позвонить, g(getx())а неg(getx().ref())
Алексей Малистов
4
@ Алексей, это не настоящая причина. Если вы это сделаете, то у вас возникнет логическая ошибка, потому что gона собирается изменить что-то, что вы больше не сможете получить.
Йоханнес Шауб -
3
@ JohannesSchaub-litb, может быть, ему все равно.
любопытный парень
« Положитесь на RVO » за исключением того, что это не называется «RVO».
любопытный парень
2
@curiousguy: Это очень приемлемый термин для этого. Нет ничего плохого в том, чтобы называть его «RVO».
Щенок
4

Злой обходной путь включает в себя ключевое слово «изменяемый». На самом деле быть злом - это упражнение для читателя. Или смотрите здесь: http://www.ddj.com/cpp/184403758

Павел
источник
3

Отличный вопрос, и вот моя попытка получить более краткий ответ (так как много полезной информации есть в комментариях и трудно разобраться в шуме.)

Любая ссылка, связанная непосредственно с временным, продлит его жизнь [12.2.5]. С другой стороны, ссылка, инициализированная с другой ссылкой, не будет (даже если она в конечном итоге будет такой же временной). Это имеет смысл (компилятор не знает, на что в конечном счете ссылается эта ссылка).

Но вся эта идея чрезвычайно запутана. Например const X &x = X();, временная xссылка const X &x = X().ref();будет длиться столько же, сколько ссылка, но НЕ (кто знает, что на ref()самом деле вернулось). В последнем случае деструктор для Xвызывается в конце этой строки. (Это можно наблюдать с помощью нетривиального деструктора.)

Таким образом, это кажется в целом сбивающим с толку и опасным (зачем усложнять правила относительно времени жизни объекта?), Но, по-видимому, была необходимость, по крайней мере, для ссылок на const, поэтому стандарт устанавливает для них такое поведение.

[Из комментария sbi ]: обратите внимание, что тот факт, что привязка его к константной ссылке увеличивает время жизни временного объекта, является исключением, которое было намеренно добавлено (TTBOMK для обеспечения ручной оптимизации). Не было добавлено исключение для неконстантных ссылок, потому что привязка временного к неконстантной ссылке, скорее всего, была ошибкой программиста.

Все временные сохраняются до конца полного выражения. Однако, чтобы использовать их, вам нужен такой же трюк, как и у вас ref(). Это законно. Кажется, нет веской причины для перехода через лишний обруч, кроме как для напоминания программисту о том, что происходит что-то необычное (а именно, эталонный параметр, модификации которого будут быстро потеряны).

[Другой комментарий sbi ] Причина, по которой Страуструп дает (в D & E) запрет на привязку значений r к неконстантным ссылкам, заключается в том, что если g () Алексея изменит объект (что можно ожидать от функции, принимающей неконстантный тип). ссылка), он изменил бы объект, который собирается умереть, так что никто не мог получить измененное значение в любом случае. Он говорит, что это, скорее всего, ошибка.

DS.
источник
2

«Ясно, что временный объект не является константой в приведенном выше примере, потому что разрешены вызовы неконстантных функций. Например, ref () может изменить временный объект».

В вашем примере getX () не возвращает const X, поэтому вы можете вызывать ref () почти так же, как вы можете вызвать X (). Ref (). Вы возвращаете неконстантную ссылку и поэтому можете вызывать неконстантные методы, но вы не можете назначить ссылку на неконстантную ссылку.

Наряду с комментарием SadSidos это делает ваши три пункта неверными.

Патрик
источник
1

У меня есть сценарий, которым я хотел бы поделиться, где я хотел бы сделать то, что просит Алексей. В плагине Maya C ++ я должен выполнить следующий шенаниган, чтобы получить значение в атрибуте узла:

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);   
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

Написание этого утомительно, поэтому я написал следующие вспомогательные функции:

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &status);
    return returnValue;
}

void operator << (MPlug& plug, MDoubleArray& doubleArray){
    MStatus status;
    MFnDoubleArrayData doubleArrayData;
    MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
    status = plug.setValue(doubleArrayObject);
}

И теперь я могу написать код с начала поста как:

(myNode | attributeName) << myArray;

Проблема в том, что он не компилируется вне Visual C ++, потому что он пытается связать временную переменную, возвращаемую из | оператор на ссылку MPlug оператора <<. Я хотел бы, чтобы это было ссылкой, потому что этот код вызывается много раз, и я бы предпочел, чтобы MPlug не копировался так много. Мне нужен только временный объект, чтобы жить до конца второй функции.

Ну, это мой сценарий. Просто подумал, что покажу пример, где хочется делать то, что описывает Алексей. Я приветствую все критические замечания и предложения!

Спасибо.

Роберт Труссарди
источник