【问题标题】:ostrstream interprets constant string as pointerostrstream 将常量字符串解释为指针
【发布时间】:2013-01-01 02:58:14
【问题描述】:

我在清理旧 C/C++ 应用程序的调试宏时遇到了这个问题:我们有一个 Tracer 类继承自 ostrstream(我知道它自 C++98 以来已被弃用,但此应用程序是用1998!)我们这样使用:

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

现在,如果链中的第一个值是上面的常量字符串,则在 Tracer 上调用 ostrstream::str() 的结果(在析构函数中完成,将结果插入队列)包含指针的十六进制表示到这个字符串而不是文本。因此上面的语句会产生类似"0x401a37 message here" 的东西。旧的宏不会出现这种情况,因为它们总是将长(线程 ID)作为第一个值,现在已被删除。

使用 gdb 进入它表明,对于第一次插入,这会在 ostrstream 上调用 operator&lt;&lt;(void const*),而随后的插入会调用 operator&lt;&lt; &lt;...&gt;(basic_ostream&lt;...&gt;&amp;, char const*)(为了便于阅读而删除了模板)。

有人可以解释这种行为吗?什么是解决这个问题的干净方法?我找到了一个简单的解决方法,它使用&lt;&lt; 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";
}

【问题讨论】:

    标签: c++ strstream


    【解决方案1】:

    之所以这样工作,是因为 Tracer() 是一个临时(右值),不能绑定到 operator&lt;&lt;(basic_ostream&lt;...&gt;&amp;, 中的非常量引用。

    但是,您可以调用像 operator&lt;&lt;(void const*) 这样的成员函数,因为这不需要左值。

    然后成员函数返回对流对象的引用可以用于调用序列中的下一个operator&lt;&lt;

    以这种方式调用任何成员函数,例如Tracer() &lt;&lt; leftTracer() &lt;&lt; flush,因此将引用“转换”为左值引用是非常安全的。

    如果你碰巧有一个兼容 C++11 的编译器,标准库甚至包含一个 operator&lt;&lt;(basic_ostream&lt;...&gt;&amp;&amp;, 来为你做这件事。在这种情况下,您不再需要解决方法。

    【讨论】:

    • 感谢您的解释 - 请参阅我对 nawaz 回答的评论,最好的解决方法是什么?
    • 不,标准的方法是使用Tracer() &lt;&lt; flush 之类的方法来获取对流的左值引用。如果你有一个最新的编译器,支持右值,它应该可以工作。这是 C++11 修订版中修复的问题。
    • 好的,谢谢。有问题的编译器是 g++ 4.1.2 RedHat,遗憾的是它不符合 c++11...
    • @l4mpi:不要使用std::ostrstream。它已弃用。也不要从流类派生。在这种情况下,组合更好。编写一个包装类,如我的回答所示。
    【解决方案2】:

    首先请注意,以const char* 为参数的operator&lt;&lt; 是一个非成员函数。并且存在一个以void const* 为参数的成员函数。

    在您的代码中,表达式Trace() &lt;&lt; "xyz" 只能调用成员函数,因为Trace() 创建了一个临时数组,它不能绑定到非成员operator&lt;&lt; 函数的第一个参数,因为这些函数采用第一个参数为std::ostream&amp;,它是非常量引用。所以 Trace() &lt;&lt; "xyz" 解析为 member operator&lt;&lt;void* 作为参数,打印地址!


    我的建议:

    • 不要从流类继承(std::ostrstream 无论如何已弃用)。
    • 宁愿写一个简单的流类包装器并重载operator&lt;&lt;

    这是一个例子:

    #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

    【讨论】:

    • 感谢您的解释...但是,我该如何解决这个问题?有没有比在所有调试语句前添加 &lt;&lt; left 更好的方法?
    • 谢谢,这将是实现它的最佳方式......虽然我们不确定我们是否会使用它,因为实际的类更大更复杂,我们的政策是对代码进行尽可能少的更改 - 这个应用程序已经有将近 15 年的历史了(当时 ostrstream 还没有被弃用 ^^),并且大多数开发它的开发人员很久以前就离开了公司,所以如果我们破坏了一些东西,我们'重新搞砸:/
    • @l4mpi: 单个operator&lt;&lt; 可以打印所有内容,只要它可以被std::ostream 打印,因为它是模板。所以这意味着它不会破坏任何东西。如果您需要额外的功能,那么您可以随时轻松添加。
    猜你喜欢
    • 2020-03-25
    • 2015-06-22
    • 1970-01-01
    • 2018-03-15
    • 1970-01-01
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 2013-08-02
    相关资源
    最近更新 更多