Почему неконстантная ссылка не может быть привязана к временному объекту?
почему нельзя получить неконстантную ссылку на временный объект,
какая функция getx() возвращает? Очевидно, что это запрещено стандартом C++
но меня интересует цель такого ограничения -не ссылки стандартные.
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;
}
- понятно, что время жизни объекта не может быть причиной, потому что
Постоянная ссылка на объект не запрещен по стандарту C++. - понятно что временный объект не является постоянным в приведенном выше примере, поскольку разрешены вызовы непостоянных функций. Например,
ref()можно изменить временный объект. - кроме того,
ref()позволяет обмануть компилятор и получить ссылку на этот временный объект и решает наши проблемы.
кроме того:
они говорят, что "присвоение временного объекта ссылке const продлевает срок службы этого объекта" и " Однако ничего не говорится о неконстантных ссылках".
Мой дополнительный вопрос. Продлевает ли следующее назначение срок службы временного объекта?
X& x = getx().ref(); // OK
11 ответов:
отсюда статья блога Visual C++ о ссылках rvalue:
... C++ не хочет, чтобы вы случайно изменение временных параметров, но напрямую вызов неконстантной функции-члена on модифицируемое значение rvalue является явным, поэтому это разрешено ...
на вашем месте я бы переосмыслил дизайн своих функций. Почему g () принимает ссылку, изменяет ли он параметр? Если нет, сделайте его постоянной ссылкой, если да, то почему вы пытаетесь передать ему временное, вам все равно, что это временное, которое вы изменяете? Почему getx () возвращается временно в любом случае? Если вы поделитесь с нами своим реальным сценарием и тем, что вы пытаетесь выполнить, вы можете получить несколько хороших предложений о том, как это сделать.
идя против языка и обманывая компилятор редко решает проблемы - обычно это создает проблемы.
Редактировать: Ответы на вопросы в комментариях: 1)X& x = getx().ref(); // OK when will x die?- Я не знаю, и мне все равно, потому что это именно то, что я подразумеваю под "идти против языка". Язык говорит: "временные умирают в конце утверждения, если они не связаны с постоянной ссылкой, и в этом случае они умирают, когда ссылка выходит за рамки". Применяя это правило, кажется, что x уже мертв в начале следующего оператора, поскольку он не привязан к ссылке const (компилятор не знает, что возвращает ref ()). Это просто догадка, однако.2) я четко сформулировал цель: вам не разрешается изменять временные файлы, потому что это просто не имеет смысла (игнорируя ссылки на C++0x rvalue). Вопрос "тогда почему мне разрешено называть неконстантных членов?- это хорошо, но у меня нет лучшего ответа, чем тот, о котором я уже говорил выше.
3) Ну, если я прав насчет x в
X& x = getx().ref();умирает в конце заявления, проблемы очевидны.в любом случае, на основе вашего вопроса и комментарии я не думаю, что даже эти дополнительные ответы удовлетворят вас. Вот заключительная попытка / резюме: комитет C++ решил, что нет смысла изменять временные файлы, поэтому они запретили привязку к неконстантным ссылкам. Может быть, некоторые реализации компилятора или исторические проблемы также были вовлечены, я не знаю. Затем появился какой-то конкретный случай, и было решено, что, несмотря ни на что, они все равно позволят прямую модификацию через вызов неконстантного метода. Но это исключение - вам, как правило, не разрешается изменять временные файлы. Да, C++ часто бывает таким странным.
в коде
getx()возвращает временный объект, так называемый"rvalue". Вы можете скопировать rvalues в объекты (ака. переменные) или привязать их к ссылкам на const (что продлит их срок службы до конца жизни ссылки). Вы не можете привязать значения rvalues к неконстантным ссылкам.Это было преднамеренное дизайнерское решение, чтобы пользователи не могли случайно изменить объект, который умрет в конце выражения:
g(getx()); // g() would modify an object without anyone being able to observeесли вы чтобы сделать это, вам нужно будет либо сделать локальную копию объекта, либо сначала привязать его к ссылке const:
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 references". Вам будет разрешено привязывать rvalues к ссылкам 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
вы показываете, что цепочка операторов разрешена.
X& x = getx().ref(); // OKвыражение 'getx().ref ();' и это выполняется до завершения перед присвоением 'x'.
обратите внимание, что getx() возвращает не ссылку, а полностью сформированный объект в локальный контекст. Объект является временным, но это не const, что позволяет вызывать другие методы для вычисления значения или иметь другие побочные эффекты случаться.
// 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());причина, по которой это можно сделать с помощью ссылки const на временный объект, заключается в том, что стандарт продлевает срок службы временного до срока службы ссылки, поэтому срок службы временных объектов продлевается за пределами конца оператора.
так что единственный оставшийся вопрос-почему не хочет ли стандарт разрешить ссылку на временные объекты для продления срока службы объекта за пределами конца утверждения?
Я считаю, что это потому, что это сделает компилятор очень трудно получить правильный для временных объектов. Это было сделано для постоянных ссылок на временные объекты, поскольку это ограничило использование и, таким образом, вынудило вас сделать копию объекта, чтобы сделать что-нибудь полезное, но обеспечивает некоторую ограниченную функциональность.
думать об этом ситуация:
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();даст вам код, который интуитивно понятен для использования и понимания.
если вы хотите изменить значение, вы должны возвратить значение переменной. Если вы пытаетесь избежать затрат на копирование obejct обратно из функции (так как кажется, что объект копируется обратно (технически это так)). Затем не беспокойтесь компилятор очень хорош в 'Оптимизация Возвращаемого Значения'
почему обсуждается C++ FAQ ( жирным шрифтом мой):
В C++, неконстантной ссылки могут привязать к lvalues и константные ссылки могут привязать к правосторонние значения или значения lvalue, но нет ничего, что можно привязать к неконстантной правосторонним значением. Это чтобы защитить людей от изменения значений временных объектов, которые уничтожаются до того, как их новое значение может быть использовано. Для пример:
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. Последнее звучит глупо, но на самом деле была такая ошибка в ранних компиляторах Fortran, которые выделили место памяти для хранения значения 0.
главная проблема в том, что
g(getx()); //errorлогическая ошибка:
gизменяет результатgetx()но у вас нет возможности изучить измененный объект. Еслиgне нужно было изменять его параметр, тогда он не требовал бы ссылки lvalue, он мог бы принять параметр по значению или по ссылке const.const X& x = getx(); // OKдействительно, потому что иногда вам нужно повторно использовать результат выражения, и довольно ясно, что вы работа с временным объектом.
однако это невозможно сделать
X& x = getx(); // errorдействующий без
g(getx())действительно, что и пытались избежать разработчики языка в первую очередь.g(getx().ref()); //OKдопустимо, потому что методы знают только о постоянстве
this, они не знают, вызываются ли они на lvalue или на rvalue.как всегда в C++, у вас есть обходной путь для этого правила, но вы должны сигнализировать компилятору, что вы знаете, что вы делаете, будучи явным:
g(const_cast<x&>(getX()));
похоже на оригинальный вопрос о почему это не допускается был дан четкий ответ:"потому что это, скорее всего, ошибка".
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 }-- Крис
злой обходной путь включает в себя ключевое слово "mutable". На самом деле быть злым остается как упражнение для читателя. Или смотрите здесь: http://www.ddj.com/cpp/184403758
Отличный вопрос, и вот моя попытка более краткого ответа (так как много полезной информации находится в комментариях и трудно выкопать в шуме.)
любая ссылка с привязкой напрямую на время продлит свою жизнь [12.2.5]. С другой стороны, ссылка, инициализированная другой ссылкой, будет не (даже если это в конечном счете то же самое временное). Это имеет смысл (компилятор не знает, что в конечном итоге ссылается на эту ссылку к.)
но вся эта идея крайне запутанна. Е. Г.
const X &x = X();будет временно до тех пор, покаxссылка, ноconst X &x = X().ref();не будет (кто знает, чтоref()фактически возвращены). В последнем случае деструктор дляXвызывается в конце этой линии. (Это можно наблюдать с помощью нетривиального деструктора.)так что это кажется вообще запутанным и опасным (зачем усложнять правила о времени жизни объекта?), но, предположительно, возникла необходимость в по крайней мере, для ссылок const, поэтому стандарт устанавливает это поведение для них.
[из sbi комментарий]: обратите внимание, что привязка его к ссылке const усиливает временные жизни-это исключение, которое было добавлено намеренно (TTBOMK для того, чтобы позволить ручную оптимизацию). Не было исключение добавлено для неконстантных ссылок, так как привязка временная чтобы неконстантная ссылка была замечена, скорее всего, программистом ошибка.
все временные значения сохраняются до конца полного выражения. Чтобы использовать их, однако, вам нужен трюк, как у вас есть с
ref(). Это законно. Кажется, что нет веской причины для дополнительного обруча, чтобы прыгать, кроме как напомнить программисту, что происходит что-то необычное (а именно, ссылочный параметр, модификации которого будут быстро потеряны).[еще sbi комментарий] причина Stroustrup дает (в D&E) для запрещения привязки rvalues к неконстантным ссылкам - это то, что если бы G () Алексея изменил объект (который вы ожидаете от функции, принимающей неконстантное значение ссылка), это изменило бы объект, который собирается умереть, поэтому никто в любом случае можно получить измененное значение. Он говорит, что это, скорее всего скорее всего, это ошибка.
"ясно, что временный объект не является постоянным в примере выше, потому что вызывает к непостоянным функциям допускаются. Например, ref() может изменить временный объект."
в вашем примере getX() не возвращает const X, поэтому вы можете вызвать ref () почти так же, как вы могли бы вызвать X ().ссылка.)( Вы возвращаете неконстантную ссылку и поэтому можете вызывать неконстантные методы, что вы не можете сделать, это назначить ссылку на неконстантную ссылку.
вместе с комментарием Садсидоса это делает ваши три пункта неверными.
у меня есть сценарий, который я хотел бы поделиться, где я хотел бы сделать то, что Алексей просит. В плагине Maya C++ мне нужно сделать следующее shenanigan, чтобы получить значение в атрибут узла:
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 оператора
Ну, это мой сценарий. Просто подумал, что я покажу пример, где хотелось бы сделать то, что Алексей описывает. Я приветствую все критические замечания и предложения!
спасибо.
Comments