【问题标题】:Custom stream manipulator that passes a character to operator overload将字符传递给运算符重载的自定义流操纵器
【发布时间】:2022-01-05 03:02:30
【问题描述】:

我在玩 shift/io 流运算符重载,我想知道是否有办法将额外的参数传递给函数,同时仍然为更简单的语法定义默认值?

考虑简单的例子:

#include <vector>
#include <iostream>

inline std::ostream& operator<<(std::ostream& ostream, const std::vector< int >& data) {
    ostream << data[0];
    for (int idx = 1; idx < data.size(); idx++) {
        ostream << "," << data[idx];   // pass ',' as argument?
    }
    return ostream;
}

我希望将分隔符 , 传递给函数,例如也许通过流修饰符:

std::cout << std::vector<int>(3, 15) << std::endl;   // 15,15,15
std::cout << delimiter(;) << std::vector<int>(3,15) << std::endl;  // 15;15;15

我编写了一个简单的类来执行此操作,但生成的语法不是很干净(需要首先调用成员运算符重载):

#include <string>
#include <vector>
#include <iostream>
#include <sstream>

class formatted{
 public:
    explicit formatted(const std::string& sequence = ",") : delimiter(sequence) { }

    template < typename T >
    std::string operator<<(const T& src) const {
        std::stringstream out;
        if (src.size()) {
            out << src[0];
            for (int i = 1; i < src.size(); i++) {
                out << delimiter << src[i];
            }
        }
        return out.str();
    }

 protected:
    std::string delimiter;
};


template < typename T >
inline std::ostream& operator<<(std::ostream& ostream, const std::vector< T >& data) {
    ostream << (formatted() << data);
    return ostream;
}



int main(int argc, char const *argv[]) {
    std::vector< int > data(10, 5);

    std::cout << data << std::endl;  // 5,5,5...5,5
    std::cout << (formatted("/") << data) << std::endl;  // 5/5/5...5/5

    return 0;
}

有没有一种方法可以简化这个过程,而不需要帮助类,或者通过使用传统的流操作符?

【问题讨论】:

    标签: c++ operator-overloading iostream manipulators


    【解决方案1】:

    对当前设计的小改进

    您正在做的事情是可能的,不能进一步简化。如果您想坚持当前的实现,我建议您修复以下问题:

    整个字符串的不必要副本

    使用

    return std::move(out).str();
    

    防止在末尾复制stringstream 中的整个字符串(C++20 起)。

    有符号/无符号比较

    for (int i = 1; i < data.size(); ...)
    

    导致有符号和无符号之间的比较,从而导致编译器警告处于足够的警告级别。 首选std::size_t i

    无约束的流插入操作符

    operator<<(const T&)
    

    不受约束并且可以接受任何类型,即使它仅适用于向量。 喜欢:

    operator<<(const std::vector<T>&)
    

    使用std::string 代替std::string_view

    const std::string& sequence = ","
    

    导致创建不必要的字符串,可能涉及堆分配,即使我们使用字符串文字,例如","。 通常我们更喜欢std::string_view,但是在这里,您将此序列存储在类中。这可能会产生问题,因为std::string_view 对字符串没有所有权,因此字符串的生命周期可能会在您使用视图之前到期。 这个问题是您的设计所固有的,不容易解决。

    另类设计

    调用formatted 流操纵器是不准确的,因为流操纵器是接受并返回basic_ios(或派生类型)的函数。您的 formatted 类型只是带有 &lt;&lt; 运算符重载的某种类型,但您也可以这样做:

    std::cout << vector_to_string(data, "/");
    

    如果没有提供参数,"/" 将默认为 ","。 问题是我们仍然没有使用流来输出字符,但我们首先要创建一个字符串,将向量内容写入其中,然后将字符串写入流中。

    相反,我们可以创建一个接受流作为其第一个参数并直接写入它的函数。 优点包括:

    • 删除#include &lt;sstream&gt;,可能只包括&lt;iosfwd&gt;
    • 性能提升
    • 可能没有堆分配
    • 没有不必要的字符串复制(C++20 之前)
    • 整体更短的代码

    这种作为常规函数的设计在标准库中也有先例。一个经典的例子是带有签名的std::getline

    template < class CharT, ...>
    std::basic_istream<CharT, ...>& getline( std::basic_istream<CharT, ...>& input,
                                             std::basic_string<CharT, ...>& str,
                                             CharT delim );
    

    通过这种设计,我们可以创建更简洁的代码,这也解决了以下问题:

    • 无法使用std::string_view
    • 必须使用 std::string 来存储整个打印的矢量
    #include <string>
    #include <vector>
    #include <iostream>
    
    template < typename T >
    std::ostream& print(std::ostream& ostream,
                        const std::vector< T >& data,
                        std::string_view delimiter = ",")
    {
        if (not data.empty()) {
            ostream << data.front();
            for (std::size_t i = 1; i < data.size(); i++) {
                ostream << delimiter << data[i];
            }
        }
        return ostream;
    }
    
    
    int main(int argc, char const *argv[])
    {
        std::vector< int > data(10, 5);
    
        print(std::cout, data) << std::endl;
        print(std::cout, data, "/") << std::endl;
    }
    

    任何范围的进一步概括

    我们可以概括此代码,使其适用于提供输入迭代器的任何范围,而不仅仅是std::vector。 这意味着我们可以将其用于std::arraystd::stringstd::vectorstd::list等类型。

    template <typename Range>
    std::ostream& print(std::ostream& ostream,
                        const Range& range,
                        std::string_view delimiter = ",")
    {
        auto begin = range.begin();
        auto end = range.end();
    
        if (begin != end) {
            ostream << *begin;
            while (++begin != end) {
                ostream << delimiter << *begin;
            }
        }
        return ostream;
    }
    

    【讨论】:

    • 令人难以置信的信息丰富的答案,谢谢。就像一个注释一样,分隔符不一定是字符串,char 对象足以满足大多数用途,并且可以消除与使用具有std::string 成员的类相关的任何性能问题。
    【解决方案2】:

    一些操作输入/输出的函数

    #include <iostream>
    #include <vector>
    #include <string>
    
    std::string Delimiter=" ";
    
    template <typename T>
    inline std::ostream& operator<<(std::ostream& ostream, const std::vector<T>& v) {
        if (!v.empty()) {
            std::ostream_iterator<T> ost(ostream, Delimiter.data());
    
            if (v.size() > 1)
                std::copy(begin(v), end(v) - 1, ost);
            ostream << v.back();
        }
    
        return ostream;
    }
    
    
    int main() {
        std::cout << std::vector<int>(3, 15) << std::endl;
        Delimiter = "!";
        std::cout << std::vector<int>(5, 15) << std::endl;
        return 0;
    }
    

    当然 Delimiter 变量不是很漂亮,但是.... 输出

    15 15 15
    15!15!15!15!15
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-05
      • 2013-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-22
      • 1970-01-01
      相关资源
      最近更新 更多