Операторы преобразования ссылочного типа: возникают проблемы? - PullRequest
14 голосов
/ 25 июня 2009

Когда я компилирую следующий код, используя g++

class A {};

void foo(A&) {}

int main()
{
  foo(A());
  return 0;
}

Я получаю следующие сообщения об ошибках:

> g++ test.cpp -o test     
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’

После некоторых размышлений эти ошибки имеют для меня большой смысл. A() - это просто временное значение, а не назначаемое место в стеке, поэтому у него не будет адреса. Если у него нет адреса, я не могу на него сослаться. Хорошо, хорошо.

Но подождите! Если я добавлю следующий оператор преобразования в класс A

class A
{
public:
  operator A&() { return *this; }
};

тогда все хорошо! Мой вопрос, насколько это безопасно? На что именно указывает this, когда A() создается как временное значение?

Я уверен в том, что

void foo(const A&) {}

может принимать временные значения в соответствии с g++ и всеми другими компиляторами, которые я использовал. Ключевое слово const всегда можно отбросить, поэтому меня удивит, если будут какие-либо фактические семантические различия между параметром const A& и параметром A&. Так что я думаю, что это еще один способ задать мой вопрос: почему const ссылка на временное значение считается компилятором безопасной, тогда как не const ссылка - нет?

Ответы [ 3 ]

16 голосов
/ 25 июня 2009

Дело не в том, что адрес не может быть получен (компилятор всегда может упорядочить его в стеке, что он делает с ref-to-const), это вопрос намерений программистов. С интерфейсом, который принимает A &, он говорит: «Я изменю то, что находится в этом параметре, чтобы вы могли читать после вызова функции». Если вы передадите его временно, то «модифицированного» объекта не будет после функции. Это (вероятно) ошибка программирования, поэтому она запрещена. Например, рассмотрим:

void plus_one(int & x) { ++x; }

int main() {
   int x = 2;
   float f = 10.0;

   plus_one(x); plus_one(f);

   cout << x << endl << f << endl;
}

Это не компилируется, но если временные файлы могут связываться с ref-to-non-const, это компилируется, но дает удивительные результаты. В plus_one (f) f будет неявно преобразован во временное int, plus_one будет брать temp и увеличивать его, оставляя базовый float нетронутым. Когда plus_one вернется, это не будет иметь никакого эффекта. Это почти наверняка не то, что задумал программист.


Правило иногда портит. Типичный пример (описанный здесь ) - попытка открыть файл, распечатать что-нибудь и закрыть его. Вы хотели бы иметь возможность:

ofstream("bar.t") << "flah";

Но вы не можете, потому что оператор << принимает ref-to-non-const. Вы можете разбить его на две строки или вызвать метод, возвращающий ref-to-non-const: </p>

ofstream("bar.t").flush() << "flah";
4 голосов
/ 25 июня 2009

Когда вы назначаете r-значение для константной ссылки, вы гарантируете, что временное не будет уничтожено, пока ссылка не будет уничтожена. При присвоении неконстантной ссылки такая гарантия не предоставляется.

int main()
{
   const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
   A& a1 = A(); // You can't do this.
}

Вы не можете безопасно отбросить константу Вилли Нилли и ожидать, что все будет работать. Существует разная семантика для константных и неконстантных ссылок.

3 голосов
/ 09 августа 2009

Признак, с которым могут столкнуться некоторые люди: компилятор MSVC (компилятор Visual Studio, проверенный в Visual Studio 2008) скомпилирует этот код без проблем. Мы использовали эту парадигму в проекте для функций, которые обычно принимали один аргумент (кусок данных для переваривания), но иногда хотели найти его и вернуть результаты вызывающей стороне. Другой режим был включен с помощью трех аргументов - вторым аргументом была информация для поиска (ссылка по умолчанию на пустую строку), а третьим аргументом были возвращаемые данные (ссылка по умолчанию на пустой список нужного типа).

Эта парадигма работала в Visual Studio 2005 и 2008, и нам пришлось реорганизовать ее так, чтобы список создавался и возвращался вместо принадлежащего вызывающему и мутировавшему для компиляции с g ++.

Если есть способ установить переключатели компилятора, чтобы запретить подобное поведение в MSVC или разрешить его в g ++, я был бы рад узнать; вседозволенность компилятора MSVC / ограниченность компилятора g ++ добавляет сложности при переносе кода.

...