ostrstream интерпретирует константу как указатель

Я столкнулся с этой проблемой при очистке макросов отладки старого приложения C/C++: у нас есть класс Tracer, унаследованный от ostrstream (Я знаю, что это устарело с C++98, но это приложение было написано в 1998 году!), Которое мы используем так:

Tracer() << "some" << " message" << " here";

Теперь, если первое значение в цепочке является константной строкой, как указано выше, результат вызова ostrstream::str() В Tracer (что делается в деструкторе, вставляя результат в очередь), содержится шестнадцатеричное представление указателя на эту строку вместо текста. Таким образом, приведенное выше утверждение даст что-то вроде "0x401a37 message here", Это не происходило со старыми макросами, так как они всегда имели long (Thread ID) в качестве первого значения, которое теперь было удалено.

Переход на него с помощью GDB показал, что для первой вставки это вызывает operator<<(void const*) на ostrstream, в то время как последующие вставки вызывают operator<< <...>(basic_ostream<...>&, char const*) (удалены шаблоны для удобства чтения).

Может кто-нибудь объяснить это поведение? Какой будет чистый способ исправить это? Я нашел легкий обходной путь, который использует << left в качестве первого аргумента - это безопасно? Есть ли лучшие способы сделать это?

Вот минимизированный пример:

#include <strstream>
#include <iostream>

using namespace std;

class Trace : public ostrstream {
    public:
        Trace();
        virtual ~Trace();
};

Trace::Trace() : ostrstream() {}
Trace::~Trace() {
    static_cast< ostrstream& >(*this) <<ends;
    char * text = ostrstream::str();
    cout << "MESSAGE: "<< text <<endl;
    delete[] text;
}

int main(){
    Trace() << "some" << " text" << " here";
    Trace() << left << "some" << " text" << " here";
    Trace() << 123 << " text" << " here";
}

2 ответа

Решение

Это работает так, потому что Tracer() является временным (rvalue), которое не может связываться с неконстантной ссылкой в operator<<(basic_ostream<...>&,,

Тем не менее, вы можете вызывать функции-члены, такие как operator<<(void const*)потому что это не требует lvalue.

Затем функция-член возвращает ссылку на объект потока, который можно использовать при вызове следующего operator<< в последовательности.

Вызов любой функции-члена таким образом, как Tracer() << left или же Tracer() << flushи, таким образом, "преобразовать" ссылку в ссылку lvalue вполне безопасно.

Если у вас есть компилятор, совместимый с C++11, стандартная библиотека даже содержит operator<<(basic_ostream<...>&&, который делает это для вас. В этом случае вам больше не нужен обходной путь.

Прежде всего обратите внимание, что operator<< который занимает const char* в качестве аргумента не является функцией-членом. И существует функция-член, которая принимает void const* в качестве аргумента.

В вашем коде выражение Trace() << "xyz" можно вызывать только функции-члены, потому что Trace() создает временную область, которая не может быть привязана к первому параметру не-члена operator<< функции, так как эти функции принимают первый аргумент как std::ostream& это неконстантная ссылка. Так Trace() << "xyz" решает член operator<< который занимает void* в качестве аргумента, который печатает адрес!


Мои советы:

  • Не наследовать от класса потока (std::ostrstream все равно не рекомендуется).
  • Скорее напишите простую обертку над потоком класса и перегрузки operator<<

Вот один пример:

#include <sstream> //for std::ostringstream

struct Trace
{
   std::ostringstream ss;

   template<typename T>
   Trace& operator << (T const & data)
   {
        ss << data;
        return *this;
   }
   ~Trace()
   {
       std::cout << ss.str() << std::endl;
   }
};

Теперь вы можете использовать его как:

Trace() << "Hello World\n" << 100 << "\nBye\n";

Выход:

Hello World
100
Bye

Live Demo

Другие вопросы по тегам