【问题标题】:Overload operator<< (unsigned char typedef as byte)重载运算符<<(无符号字符类型定义为字节)
【发布时间】:2023-04-09 04:24:01
【问题描述】:

我想重载(劫持?)ostreambasic_ostream&lt;unsigned char&gt;,以便它停止尝试将八位字节(无符号字符)显示为可打印字符。

我和cout 以及在屏幕上放笑脸的朋友一起生活太久了。而且我厌倦了使用演员表:hex &lt;&lt; int(0xFF &amp; b) &lt;&lt; ...

是否可以覆盖标准行为?我已经尝试过模板和非模板覆盖。它们编译,但似乎没有被调用。

【问题讨论】:

    标签: c++ operator-overloading iostream


    【解决方案1】:

    问题是已经有一个

    template<class charT, class traits>
    std::basic_ostream<charT,traits>& 
    operator<<(std::basic_ostream<charT,traits>&, charT);
    

    namespace std。由于basic_ostream&lt;&gt; 也在此命名空间中,因此当您输出unsigned char 时,ADL 会选择它。添加您自己的重载可能会使调用运算符不明确,或者您的重载将被默默地忽略。

    但即使它可以工作,它也会很脆弱,因为忘记一个包含可能会巧妙地改变代码的含义,而无需编译器进行任何诊断。
    还有更多:每个查看此类代码的维护程序员都会假设调用了标准运算符(当他向代码添加另一个输出语句时,永远不会考虑添加包含)。
    简而言之,最好添加一个函数来做你想做的事。

    一个合理的语义替代方案可能是添加一个调用所需输出格式的流操纵器。不过,我不确定这在技术上是否可行。

    【讨论】:

    • @sbi:尽管您解释了它不应该如何工作,但重载与 MSVC 配合得很好,并且与 Comeau Online 一起编译,所以我只是将其发布为答案。干杯,
    • @Alf:没有人怀疑它编译,问题是它调用了哪个。我不会相信VC。不过,我可能是错的,而 VC 可能是对的。
    • 我花时间发布了一个完整的答案。我从 Als 是问题的唯一答案开始,所以我不能说我在写答案之前看到了这个答案的最后一句话(添加了一个操纵器)。也花了很多时间来记住正确的方法。在过去,我写了很多 很多 的零票或一票答案,在大多数 SO 社区转向其他问题之后,我发布了这些答案。事情就是这样。
    • @sbi:也可以很好地与 g++ 4.4.1 配合使用。所以,我认为你错了。当然,如果我不太确定,我不会发布它,但这样我可以让其他人做繁重的工作,在标准中查找相关条款......
    • 顺便说一句,@sbi,当我对某事发表评论并且回答者编辑答案作为回应时,我的 StackExchange“收件箱”中没有任何内容,所以我可能不知道“请纠正这个”评论需要被删除。这是收件箱中的设计缺陷,没有简单的解决方案,恕我直言。
    【解决方案2】:

    卢克是正确的。

    如果您不介意十进制输出,您当前方法的更快替代方法是将 char 提升为 int

    unsigned char c = '!';
    os << +c;
    

    我不明白这会是怎样的征税!

    【讨论】:

    • 能解释一下这是如何解析的吗?它甚至编译似乎并不明显(即使它编译:ideone.com/TzV3N)。
    • @Matthieu 你能解析int i = -j;吗? int i = +j; 呢? (这是一元 operator+。)
    • @Matthieu:为什么不呢?那是一元+,并且执行了一个整体提升。您可以在[2003: 5.3.1/6] 阅读所有相关信息。
    • @Luc:我知道,但我仍然相信这远非显而易见。由于+ 是多余的,因此除了 DSL 之外很少(如果有的话)使用它。
    • @Tomalak:为什么不简单地使用 int(c) ?实际上,还有 4 个字符,但没有让读者深入研究完整的促销规则来理解代码,这使得它更易于访问!
    【解决方案3】:

    Als 是对的,你所要求的不会发生。

    您可以做的最好的事情是write your own IO manipulator (iomanip) to do the magic for you. 在这种情况下,您需要一个接受unsigned char 的函数(尽管我强烈建议使用&lt;stdint.h&gt; 中的uint8_t) .

    #include <stdint.h>
    #include <ostream>
    
    class asHex
    {
    public:
        asHex(uint8_t theByte): value(theByte) {}
        void operator()(std::ostream &out) const 
            { std::ios::fmtflags oldFlags = out.flags; out << std::hex 
                  << std::setw(2) << std::setfill('0') << std::uppercase << theByte; 
              out.flags(oldFlags); }
    private:
        uint8_t theByte;
    };
    
    std::ostream& operator<<(std::ostream &out, asHex number)
    {
        number(out); return out;
    }
    

    然后你可以写:

    cout << asHex(myByte);
    

    您可以将构造函数添加到 asHex,甚至可以将其作为模板类来支持 16、32 和其他位数。

    (是的,我知道&lt;stdint.h&gt; 不是官方的 C++ 标头,但我宁愿在全局命名空间中定义其定义,而不是 std::,而不必执行 using namespace std; 转储 所有内容 在全局命名空间中。)

    【讨论】:

    • 如果你这样做了,你就不需要整个机械手,把原始流弄乱了。只需创建一个函数std::string asHex(unsigned char) 即可。容易得多。
    • 这就变成了“哪个更好,创建一个临时的std::string 还是一个不接触堆的临时对象?”的问题?如果没有使用上下文,我无法回答。在我的回答中,我仍然更喜欢基于类的方法,因为它不涉及堆分配,并且更有可能被编译器优化。
    • 我们在这里谈论的是输出,通常是到终端或文件中。我怀疑std::string 的分配是否与此常见用例相关。无论如何,如果是,只需返回 struct foo { char[2] string; }; 并为其重载 &lt;&lt;。这并没有改变我写的东西。您的方法 OTOH 的缺点是它操纵了流的模式。我永远记不起这些操纵器中的哪一个是永久性的,但我相信至少其中一些是永久性的。这很糟糕,因为您的操纵器的用户需要记住重置他们没有自己设置的流模式。
    • 所以oldFlags = out.flags; ... out.flags(oldFlags); 没有充分保存和恢复流状态?不知道那个。这一直是我对 iostream 最不满意的地方之一:很难将输出模块化并且不把事情搞砸。我仍然认为格式化和 I/O 应该相互分离:一组 API 用于制作格式化的std::string,另一组用于将该字符串发送到某处。就像 Python 的 str.format().
    • 令我非常尴尬的是,我只是忽略了你的那部分代码。 OTOH,我真的不知道这是否足够。可能是,也可能不是,而且没有 J. Kanze、D. Kühl 或对 Langer/Kreft 的彻底研究,我不知道该相信谁对此事的看法。 (谁真正了解pword/iword 的东西?)我还认为 iostreams 库不如语言的单一通用输入/输出库应有的好,并且缺少保护流整个状态的能力只是一个问题。
    【解决方案4】:
    #include <iostream>
    #include <string>       // std::char_traits
    
    typedef unsigned char       UChar;
    typedef UChar               Byte;
    
    typedef std::char_traits<char>      CharTraits;
    typedef std::char_traits<wchar_t>   WCharTraits;
    
    typedef std::basic_ostream< char, CharTraits >      CharOStream;
    typedef std::basic_ostream< wchar_t, WCharTraits >  WCharOStream;
    
    CharOStream& operator<<( CharOStream& stream, UChar v )
    {
        return stream << v+0;
    }
    
    int main()
    {
        char const      c   = 'c';
        UChar const     u   = 'u';
    
        std::cout << c << '\n' << u << std::endl;
    }
    

    这与 MSVC 10.0 和 MinGW g++ 4.4.1 配合得很好,并且可以在 Comeau Online 中干净地编译,所以我相信它在形式上是可以的。

    干杯,

    【讨论】:

    • 你为什么要把std::char_traits拖进去,@Alf? std::basic_ostream&lt;char&gt; 不也能正常工作吗?
    • 另外,虽然这可能在语法上有效,但它确实具有我在回答中提到的语义缺点。
    【解决方案5】:

    由于 ADL,标准 operator&lt;&lt; 将被调用。尝试明确限定您的调用:

    ::operator<<(os, 42);
    

    【讨论】:

    • @Jan 现在他可以打电话给他的接线员了。
    • 大声笑,是的。这是一个解决方案。当然,它不是一个很好的,但它是一个。
    【解决方案6】:

    您不能直接覆盖std::cout 的行为。如果任何开发代码都可以改变其他代码使用的标准库的行为,那就太容易出错了。

    可以创建自己的类来模拟std::cout 的行为,并改用该对象。

    class SpecialCout
    {
        template <typename T>
        friend SpecialCout& operator<< ( SpecialCout const& scout, T const &t )
        {
            // Do any adjustments to t here, or decide to return early.
    
            std::cout << t;
            return *this;
        }
    
    };
    
    extern SpecialCout scout;
    

    【讨论】:

    • 为什么要限制自己使用std::cout
    猜你喜欢
    • 2011-09-29
    • 1970-01-01
    • 1970-01-01
    • 2014-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-04
    相关资源
    最近更新 更多