是否可以在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::setfill
和os.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
答案 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
请注意,上述实现并不打算用作其生命周期仅限于一行代码的临时目录。如果您不喜欢这样做,则必须添加某种机制以将流刷新到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)
有一种方法可以在流对象上存储自定义数据,但这并不漂亮:iword
和pword
接口。
#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
#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;
}