Почему запись в объект с временным строковым потоком только печатает адреса объектов?
Следующий фрагмент - это упрощенная версия используемого регистратора. Он расширяет std::ostringstream
и может быть заполнен с помощью <<
-оператора. После уничтожения все содержимое записывается в std::cout
.
Написание (<<
) непосредственно во временный объект Logger()
, я ожидаю, что он напечатает этот ввод, однако он только печатает адрес чего-то на std::cout
. При записи в ссылку временного объекта Logger().stream()
работает как ожидалось.
Почему это происходит?
Btw, это поведение происходит только на С++ 98-land (ideone), которое я должен использовать. С С++ 11 (coliru) и С++ 14 (ideone) оба вызова варианты работают так, как ожидалось. Что по-другому в С++ 11/14?
#include <iostream>
#include <sstream>
class Logger : public std::ostringstream
{
public:
~Logger()
{
std::cout << this->str() << std::endl;
}
Logger& stream()
{
return *this;
}
};
int main( int argc, char ** argv )
{
// 1.
// Prints an address, e.g. 0x106e89d5c.
Logger() << "foo";
// 2.
// Works as expected.
Logger().stream() << "foo";
// What is the difference between 1. and 2.?
return 0;
}
Ответы
Ответ 1
operator<<
, который обрабатывает вставку const char *
, является шаблоном без члена:
template< class Traits >
basic_ostream<char,Traits>& operator<<(basic_ostream<char,Traits>& os, const char* s);
Он берет свой поток по ссылке non-const (lvalue), которая не привязана к временным.
В С++ 98/03 лучшей жизнеспособной функцией является член operator<<(const void *)
, который печатает адрес.
В С++ 11 и более поздних версиях библиотека предоставляет специальный поток operator<<
для rvalue:
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
который выполняет os << value
и возвращает os
, по существу выполняя операцию вывода в потоке lvalue.
Ответ 2
Соответствующие факты:
-
Logger()
- значение r, но Logger().stream()
- значение l.
-
operator<<
, который берет указатель и печатает его адрес, является членом ostream&
, тогда как operator<<
, который принимает const char*
и печатает строку, является свободной функцией,
template<class traits>
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out,
const char* s);
Обратите внимание, что первый аргумент является ссылкой на константу без ссылки, поэтому он не может привязываться к значению r. Поэтому, если поток является значением r, эта перегрузка не является жизнеспособной. Следовательно, const char*
преобразуется в const void*
и его адрес печатается. Когда вы используете Logger().stream()
, который является lvalue, эта перегрузка выигрывает и строка печатается.
В С++ 11 добавлен новый оператор ввода потока rvalue:
template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);
с эффектом os << x
. Теперь эта перегрузка побеждает в Logger() << "foo"
и пересылает аргумент, как будто поток был lvalue. Тогда вызывается свободная функция, заданная ранее.
Ответ 3
С++ 11 добавил, что это перегружено non-member operator<<
:
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
Теперь оператор, который, по вашему мнению, вы вызываете в случае Logger()
, выглядит следующим образом:
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
Это работает для случая Logger().stream()
, потому что это ссылка lvalue, но это не работает для случая Logger() << "foo"
. Logger()
не может связываться с ссылкой lvalue. Там единственная жизнеспособная перегрузка - member operator<<
:
basic_ostream& operator<<( const void* value );
который печатает адрес.