Подтвердить что ты не робот

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

Следующий фрагмент - это упрощенная версия используемого регистратора. Он расширяет 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;
}
4b9b3361

Ответ 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 );

который печатает адрес.