Операторы преобразования ссылочного типа: возникают проблемы?

StackOverflow https://stackoverflow.com/questions/1040950

Вопрос

Когда я компилирую следующий код, используя 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, когда const создается как временное значение?

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

void foo(const A&) {}

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

Это было полезно?

Решение

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

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:

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

Другие советы

Когда вы назначаете 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.
}

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top