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

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

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;
}
  • Ясно, что время жизни объекта не может быть причиной, потому что постоянная ссылка на объект не запрещена стандартом С++.
  • Ясно, что временный объект не является константой в примере выше, потому что допускаются вызовы на непостоянные функции. Например, ref() может изменить временный объект.
  • Кроме того, ref() позволяет обмануть компилятор и получить ссылку на этот временный объект и который решает нашу проблему.

Кроме того:

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

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

Ответ 1

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

... С++ не хочет, чтобы вы случайно изменять временные, но напрямую вызов функции non-const member на модифицируемое rvalue является явным, поэтому это позволило...

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

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

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

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

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

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

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

Ответ 2

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

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

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

Обратите внимание, что следующий стандарт С++ будет включать ссылки rvalue. То, что вы знаете как ссылки, становится, таким образом, называться "ссылками на lvalue". Вам будет разрешено связывать 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 data, leaving the temporary object empty

Ответ 3

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

 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 обратно из функции (как кажется, объект копируется обратно (технически это)). Тогда не беспокойтесь, компилятор очень хорош в "Оптимизация возвращаемого значения"

Ответ 4

Почему обсуждается в часто задаваемые вопросы по С++ (boldfacing):

В С++ ссылки non-const могут связываться с lvalues, а ссылки const могут связываться с lvalues ​​или rvalues, но нет ничего, что может связываться с неконстантным значением r. Это , чтобы защитить людей от изменения значений временных объектов, которые были уничтожены до того, как их новое значение можно использовать. Например:

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.

Ответ 5

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

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.

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

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

Ответ 6

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

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
}

- Крис

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

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

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

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

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

Ответ 10

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

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

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

Ответ 11

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

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 С++, потому что он пытается привязать временную переменную, возвращенную из | оператора к ссылке MPlug < оператор. Я бы хотел, чтобы это была ссылка, потому что этот код вызывается много раз, и я бы предпочел не копировать MPlug. Мне нужен только временный объект для жизни до конца второй функции.

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

Спасибо.