如何在<<运算符中为自定义对象添加自定义前缀

时间:2018-09-28 19:40:26

标签: c++

是否可以在operator<<中为我实现的对象添加自定义前缀?

例如:

class A {
   public:
    std::string id;
    int count;
};

std::ostream &operator<<(std::ostream &os, const A &a)
{
    os << os.prefix() << "Id: " << a.id << "\n";
    os << os.prefix() << "Count: " << a.count << "\n";
    return os;
}

如果我做这样的事情:

A a;
a.id = "foo";
a.count = 1;
std::cout << a << std::endl;

输出将是:

Id: foo
Count: 1

我想做类似的事情:

std::cout << set_prefix(" -") << a << std::endl;
std::cout << set_prefix("==>") << a << std::endl;

要获得这样的输出:

 -Id: foo
 -Count: 1
==>Id: foo
==>Count: 1

建议使用std::setfillos.fill,但是std::setfill以一个char作为参数,而我需要一个自定义字符串。

解决方案

通过查看operator<<(std::basic_ostream)文档,我发现了这一点:

  

在插入之前,首先,使用扩展所有字符   os.widen(),则填充确定如下:   要插入的字符少于os.width(),则足够的副本   os.fill()被添加到字符序列以使其长度   等于os.width()。如果为(out.flags()&std::ios_base::adjustfield) == std::ios_base::left,则将填充字符添加到   输出序列,否则将它们添加到输出序列之前。   插入后,调用width(0)以取消   std::setw(如果有)。

所以对我有用的解决方案是在开始时保存流的原始宽度,而不是在必要时恢复它们。

std::ostream &operator<<(std::ostream &os, const A &a)
{
    auto w = os.width();
    os << std::setw(w) << "" << "Id: " << a.id << "\n";
    os << std::setw(w) << "" << "Count: " << a.count;
    return os;
}

然后:

std::cout << a << std::endl;
std::cout << std::setw(4) << a << std::endl;
std::cout << std::setfill('>') << std::setw(2) << a << std::endl;

给出以下输出:

Id: foo
Count: 1
    Id: foo
    Count: 1
>>Id: foo
>>Count: 1

5 个答案:

答案 0 :(得分:3)

也许有点矫kill过正,但是您可以使用以下方法:

#include <iostream>
#include <sstream>

struct line_buffered_stream {
    std::ostream& out;
    std::stringstream ss;
    std::string prefix;
    line_buffered_stream(std::ostream& out,std::string prefix) : 
        out(out),prefix(prefix) {}        
    template <typename T> 
    auto operator<<(const T& t) -> decltype(this->ss << t,*this) { 
        ss << t; 
        return *this;
    }        
    ~line_buffered_stream(){
        std::string line;
        while (std::getline(ss,line)){
            out << prefix << line << "\n";
        }
    }
};

int main() {
     line_buffered_stream(std::cout,"==>") << "a\nb\n";
     line_buffered_stream(std::cout,"-->") << "a\nb\n";        
}

输出:

==>a
==>b
-->a
-->b

Live Demo

请注意,上述实现并不打算用作其生命周期仅限于一行代码的临时目录。如果您不喜欢这样做,则必须添加某种机制以将流刷新到std::cout,而不必等到析构函数被调用。

答案 1 :(得分:2)

我不知道使用字符串执行此操作的任何方法,但是如果您只满足于char,则看起来可以使用std::setfill操纵器,并且在重载中使用fill字符:

std::cout << std::setfill('-') << a << std::endl;

std::ostream &operator<<(std::ostream &os, const A &a)
{
    os << os.fill() << "Id: " << a.id << "\n";
    os << os.fill() << "Count: " << a.count << "\n";
    return os;
}

答案 2 :(得分:1)

我不太喜欢它,因为它使用了全局变量,但是确实允许您让其他类使用相同的方法,他们只需要正确地编写自己的operator <<。当您想从打印中清除前缀时,它还要求您调用set_prefix("");。就是说,它确实允许您在输出之前添加任何想要的字符串。

namespace details
{
    // we neeed this for tag dispatch
    struct Prefix {};
    // this will be used in the class(es) operator << for the line prefix
    std::string prefix;
    // allows set_prefix to be called in the output stream by eating it return and returning the stream as is
    std::ostream& operator <<(std::ostream& os, const Prefix& prefix)
    {
        return os;
    }
}

// set the prefix and return a type that allows this to be placed in the output stream
details::Prefix set_prefix(const std::string& prefix)
{
    details::prefix = prefix;
    return {};
}

class A {
   public:
    std::string id;
    int count;
};

std::ostream &operator<<(std::ostream &os, const A &a)
{
    os << details::prefix << "Id: " << a.id << "\n";
    os << details::prefix << "Count: " << a.count << "\n";
    return os;
}

int main()
{
    A a;
    a.id = "foo";
    a.count = 1;
    std::cout << a << std::endl;
    std::cout << set_prefix(" -") << a << std::endl;
    std::cout << set_prefix("==>") << a << std::endl;
}

输出:

Id: foo
Count: 1

 -Id: foo
 -Count: 1

==>Id: foo
==>Count: 1

答案 3 :(得分:1)

有一种方法可以在流对象上存储自定义数据,但这并不漂亮:iwordpword接口。

stream_prefix.hpp:

#ifndef STREAM_PREFIX_HPP_
#define STREAM_PREFIX_HPP_

#include <utility>
#include <string>
#include <ostream>

namespace stream_prefix_details {
    class set_prefix_helper {
    public:
        explicit set_prefix_helper(std::string prefix)
            : m_prefix(std::move(prefix)) {}
    private:
        std::string m_prefix;

        // These insertion operators can be found by Argument-Dependent Lookup.
        friend std::ostream& operator<<(
            std::ostream&, set_prefix_helper&&);
        friend std::ostream& operator<<(
            std::ostream&, const set_prefix_helper&);
    };
}

// The set_prefix manipulator. Can be used as (os << set_prefix(str)).
inline auto set_prefix(std::string prefix)
    -> stream_prefix_details::set_prefix_helper
{ return stream_prefix_details::set_prefix_helper{ std::move(prefix) }; }

// Get the prefix previously stored by (os << set_prefix(str)), or
// an empty string if none was set.
const std::string& get_prefix(std::ostream&);

#endif

stream_prefix.cpp:

#include <stream_prefix.hpp>

namespace stream_prefix_details {
    int pword_index() {
        static const int index = std::ios_base::xalloc();
        return index;
    }

    void stream_callback(std::ios_base::event evt_type,
                         std::ios_base& ios, int)
    {
        if (evt_type == std::ios_base::erase_event) {
            // The stream is being destroyed, or is about to copy data
            // from another stream. Destroy the prefix, if it has one.
            void*& pword_ptr = ios.pword(pword_index());
            if (pword_ptr) {
                delete static_cast<std::string*>(pword_ptr);
                pword_ptr = nullptr;
            }
        } else if (evt_type == std::ios_base::copyfmt_event) {
            // The stream just copied data from another stream.
            // Make sure we don't have two streams owning the same
            // prefix string.
            void*& pword_ptr = ios.pword(pword_index());
            if (pword_ptr)
                pword_ptr =
                    new std::string(*static_cast<std::string*>(pword_ptr));
        }
        // Can ignore imbue_event events.
    }

    std::ostream& operator<<(std::ostream& os,
                             set_prefix_helper&& prefix_helper)
    {
        void*& pword_ptr = os.pword(pword_index());
        if (pword_ptr)
            *static_cast<std::string*>(pword_ptr) =
                std::move(prefix_helper.m_prefix);
        else {
            os.register_callback(stream_callback, 0);
            pword_ptr = new std::string(std::move(prefix_helper.m_prefix));
        }
        return os;
    }

    std::ostream& operator<<(std::ostream& os,
                             const set_prefix_helper& prefix_helper)
    {
        void*& pword_ptr = os.pword(pword_index());
        if (pword_ptr)
            *static_cast<std::string*>(pword_ptr) = prefix_helper.m_prefix;
        else {
            os.register_callback(stream_callback, 0);
            pword_ptr = new std::string(prefix_helper.m_prefix);
        }
        return os;
    }
}

const std::string& get_prefix(std::ostream& os)
{
    void* pword_ptr = os.pword(stream_prefix_details::pword_index());
    if (pword_ptr)
        return *static_cast<std::string*>(pword_ptr);
    else {
        // This string will never be destroyed, but it's just one object.
        // This avoids the Static Destruction Order Fiasco.
        static const std::string* const empty_str = new const std::string;
        return *empty_str;
    }
}

用法:

#include <iostream>
#include <stream_prefix.hpp>

class A {
   public:
    std::string id;
    int count;
};

std::ostream &operator<<(std::ostream &os, const A &a)
{
    os << get_prefix(os) << "Id: " << a.id << "\n";
    os << get_prefix(os) << "Count: " << a.count << "\n";
    return os;
}

int main() {
    A a;
    a.id = "foo";
    a.count = 1;
    std::cout << a << std::endl;
    std::cout << set_prefix("==> ") << a << std::endl;
}

完整的演示here

请注意,此set_prefix操作器是“粘性”的,这意味着该设置将在使用后保留在流上,就像除std::setw之外的大多数标准操作器一样。如果要在完成输出A对象描述后重置它,只需将os << set_prefix(std::string{});添加到operator<<函数中即可。

答案 4 :(得分:0)

这行得通,但是它是非常非常丑陋和可怕的代码。

一对问题:
-operator <<必须在类外定义,因为您希望将类A作为rhs参数,而不是像A :: operator <<()那样调用它,而实际上将第二个A类作为论据。
-cout无法处理void输出,因此,由于您坚持使用cout逗号链接设置前缀,因此它必须返回一个空字符串对象。
-如果您不希望记住前缀,只需在操作符<<定义的末尾添加prefix.clear()。

    class A
    {
    public:
         std::string id;
         std::string prefix;
         int count;

         std::string set_prefix(const std::string& inp) 
         {
              prefix = inp;
              return std::string();
         }


         std::string get_prefix() const
         {
              return prefix;
         }

    };

std::ostream &operator<<(std::ostream &os, const A &input)
{

    os << input.get_prefix() << "Id: " << input.id << "\n";
    os << input.get_prefix() << "Count: " << input.count << "\n";
    return os;
}


int main()
{
     A class1;
     class1.id = "test";
     class1.count = 5;
     std::cout << class1.set_prefix(" -") << class1; // endl removed, as your operator<< definition already has a "\n" at the end.
     std::cout << class1.set_prefix("==>") << class1;
}