Операторы преобразования ссылочного типа: возникают проблемы?
-
22-07-2019 - |
Вопрос
Когда я компилирую следующий код, используя 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 ++ добавляет сложности при переносе кода.