我以前已经在Stack Overflow上发布了此文档,并正在考虑将其提交给Boost以进行更广泛的分发,但认为也许最好先在这里进行同行评审,然后看看是否可以先进行明显的改进。

// infix_iterator.h
// 
#if !defined(INFIX_ITERATOR_H_)
#define  INFIX_ITERATOR_H_
#include <ostream>
#include <iterator>

template <class T,
          class charT=char,
          class traits=std::char_traits<charT> >

class infix_ostream_iterator :
    public std::iterator<std::output_iterator_tag,void,void,void,void>
{
    std::basic_ostream<charT,traits> *os;
    charT const* delimiter;
    bool first_elem;
public:
    typedef charT char_type;
    typedef traits traits_type;
    typedef std::basic_ostream<charT,traits> ostream_type;

    infix_ostream_iterator(ostream_type& s)
        : os(&s),delimiter(0), first_elem(true)
    {}
    infix_ostream_iterator(ostream_type& s, charT const *d)
        : os(&s),delimiter(d), first_elem(true)
    {}
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item)
    {
        // Here's the only real change from ostream_iterator:
        // We don't print the delimiter the first time. After that, 
        // each invocation prints the delimiter *before* the item, not
        // after. As a result, we only get delimiters *between* items,
        // not after every one.
        if (!first_elem && delimiter != 0)
            *os << delimiter;
        *os << item;
        first_elem = false;
        return *this;
    }

    infix_ostream_iterator<T,charT,traits> &operator*() {
        return *this;
    }
    infix_ostream_iterator<T,charT,traits> &operator++() {
        return *this;
    }
    infix_ostream_iterator<T,charT,traits> &operator++(int) {
        return *this;
    }

};

#endif 


这是(至少打算)代替std::ostream_iterator的唯一替代品,唯一的区别是(至少在正常使用时)它是仅在项目之间而不是在每个项目之后打印定界符。使用它的代码如下所示:

#include "infix_iterator.h"

std::vector<int> numbers = {1, 2, 3, 4};

std::copy(begin(numbers), end(numbers), 
          infix_ostream_iterator<int>(std::cout, ", "));


这样做的动机非常简单-有了std::ostream_iterator,您的列表就会像1, 2, 3, 4,一样出现,但是有了infix_iterator,作为旁注,尽管我在此演示代码中使用了几个C ++ 11功能,但我相信迭代器本身对于C ++应该很好03-尽管如果有人看到对没有C ++ 11支持的编译器有问题的任何东西,我也想听听。

编辑:如果有人关心,这里是新版本结合了@Konrad和@Loki的输入。谢谢大家。

// infix_iterator.h
#if !defined(INFIX_ITERATOR_H_)
#define  INFIX_ITERATOR_H_
#include <ostream>
#include <iterator>
#include <string>

template <class T, class charT=char, class traits=std::char_traits<charT> >
class infix_ostream_iterator :
    public std::iterator<std::output_iterator_tag, void, void, void, void>
{
    std::basic_ostream<charT,traits> *os;
    std::basic_string<charT> delimiter;
    std::basic_string<charT> real_delim;

public:

    typedef charT char_type;
    typedef traits traits_type;
    typedef std::basic_ostream<charT, traits> ostream_type;

    infix_ostream_iterator(ostream_type &s)
        : os(&s)
    {}

    infix_ostream_iterator(ostream_type &s, charT const *d)
        : os(&s), 
          real_delim(d)
    {}

    infix_ostream_iterator<T, charT, traits> &operator=(T const &item)
    {
        *os << delimiter << item;
        delimiter = real_delim;
        return *this;
    }

    infix_ostream_iterator<T, charT, traits> &operator*() {
        return *this;
    }

    infix_ostream_iterator<T, charT, traits> &operator++() {
        return *this;
    }

    infix_ostream_iterator<T, charT, traits> &operator++(int) {
        return *this;
    }
};

#endif 


评论

为什么要使用charT const * d而不是const std :: basic_string &?

@Dave:要模仿std :: ostream_iterator的界面?

您是否已提交给Boost上游?我似乎在Boost中找不到infix_iterator和infix_ostream_iterator。

@sukhmel:否-可以说,我在其他方面有些捉襟见肘,可以说是裂开了。

@JerryCoffin:这是最有用的工具;我真的很讨厌那些落后的delim。如果在SO的答案中使用缩小版本的版本,是否可以链接到这里? :)

#1 楼

没有什么专业(只是一些个人意见):

一致的间距

    infix_ostream_iterator(ostream_type& s)
        : os(&s),delimiter(0), first_elem(true)
    {}  //    ^^^ No Space   ^^^Trailing space


一致的类型命名

// Here we have & on the left
infix_ostream_iterator<T,charT,traits>& operator=(T const &item)

// Here we have it on the right
infix_ostream_iterator<T,charT,traits> &operator*() {


delimter的拼写


我在编写此代码时错了很多=> delimiter

更容易阅读初始化程序列表

就像我更喜欢每行一个语句,我更喜欢在初始化程序列表中每行初始化一个变量(更易于阅读)。

它可能对性能没有影响,但我将删除if,因此代码如下所示:

infix_ostream_iterator(ostream_type& s, charT const *d)
    : os(&s)
    , delimiter(d)
    , first_elem(true)
{}


在构造中,actualDelimter指向用户提供的字符串(或空字符串),而delimter设置为指向空字符串。

评论


\ $ \ begingroup \ $
我一直在想这个。而不是直接存储用户的指针,并初始化定界符以指向空字符串,您是否看到我不对定界符和real_delim都使用std :: basic_string 的问题?优点:不需要初始化为空字符串,即使用户的指针不需要,也保持有效。缺点:开销可能增加了吗?
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2012年7月1日14:15



\ $ \ begingroup \ $
@JerryCoffin:我认为没有不利的一面。间接费用是在施工过程中发生的。即使大量复制,复制的额外成本也不会那么高。而使用operator <<完全不计所有这些
\ $ \ endgroup \ $
–马丁·约克
2012年7月1日在22:21



\ $ \ begingroup \ $
说副本比他以前的条件便宜是真的合理的假设吗?有什么方法可以量化吗?
\ $ \ endgroup \ $
–大卫
2012年7月3日于13:03

\ $ \ begingroup \ $
@戴夫。它并不便宜,而是更昂贵。但是您不会期望看到太多复制(就像我想说的那样)。因此,与通过这种方式实现的额外安全性相比,我认为额外的成本微不足道。
\ $ \ endgroup \ $
–马丁·约克
2012年7月3日15:26



\ $ \ begingroup \ $
我将delimiter和real_delim保留为charT const *,但在构造函数中的操作如下:infix_ostream_iterator(ostream_type&s,charT const * d = 0):os(&s),delimiter(“”),real_delimiter(d ?d:“”){}。这样一来,复制的成本就更少了。
\ $ \ endgroup \ $
– dalle
2014年1月22日7:30



#2 楼

我在代码中唯一可以批评的是infix运算符与指针/引用声明之间空格的不一致位置。

空格。

class charT=char,
public std::iterator<std::output_iterator_tag,void,void,void,void>


…等,空格使用不一致。

: os(&s),delimiter(0), first_elem(true)


…等,不一致&的位置。

我还将统一使用函数定义之间的空白行,并删除template <…>块与类标题之间以及类定义末尾的空白行。

肯定是挑剔的,但这实际上是唯一值得批评的地方。

#3 楼

我还要声明第一个构造函数为显式的:地点。

#4 楼

保持const char指针
我同意Lokis建议的更改,除了我将delimiterreal_delim保留为charT const *并将构造函数更改为:
infix_ostream_iterator(ostream_type &s, charT const *d = 0)
 : os(&s)
 , delimiter("")
 , real_delim(d ? d : "")
{
}

那样,我们减少了复制字符串,而是只复制一个指针。
推导的类型
模仿在C ++ 14中对std :: less所做的更改,您可以将operator=设为模板,这样我们就无需指定这种类型的类。但这会破坏与std::ostream_iterator接口的兼容性。
template <class T>
infix_ostream_iterator<charT, traits> &operator=(T const &item)
{
    *os << delimiter << item;
    delimiter = real_delim;
    return *this;
}


评论


\ $ \ begingroup \ $
可以说d的类型作为模板参数可能更好。我可以设想在某些代码中将其作为字符串文字(const char *),而在其他代码中将其作为std :: string。
\ $ \ endgroup \ $
– Toby Speight
17 Mar 9 '17 at 9:10

#5 楼

首先,这堂课的好主意!我同意其他用户(Loki,Konrad,user15108,dalle)已经提出的更改。

此外,我注意到infix_ostream_iterator不是默认可构造的。这适合用例,并遵循std::ostream_iterator的设计。因此,您也可以将std::basic_ostream存储为参考成员变量。也就是说,现在您可以删除指针间接寻址,这将简化剩余的代码。例如,构造函数变为

class infix_ostream_iterator : ... {
    ...
    std::basic_ostream<charT,traits>& os;
    ...
}


,您可以直接写入流中

infix_ostream_iterator(ostream_type &s, charT const *d)
    : os(s)
    , real_delim(d)
{}


实现细节(您的界面相同)。我认为编译器无论如何都会生成相同的指令集。

期待看到类似Boost的此类!祝您好运。

#6 楼

我知道我已经晚了几年,但是我想提出一个主意:为什么分隔符和非分隔符必须为同一类型?您的初始版本使用的是charT const*,而您的新版本使用的是std::basic_string<charT> ...,但要复制两个string只是为了处理没有定界符,这似乎很浪费。我建议您使用一个重载的make_*函数,它可以创建您的迭代器或创建标准的迭代器:您的类型始终使用定界符。

评论


\ $ \ begingroup \ $
也许只是我还没醒,但是我想我不确定这将如何工作/使用。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
15年12月11日在16:25

\ $ \ begingroup \ $
如果要使用定界符:make_ostream_iterator (std :: cout,“,”),如果您不需要make_ostream_iterator (std :: cout)。然后,您的对象只能使用定界符构造。
\ $ \ endgroup \ $
–巴里
2015年12月11日下午16:27

\ $ \ begingroup \ $
这里的想法是能够支持诸如copy(b,e,out)之类的东西,并使[b ... e]的元素像a,b,c之类的东西出现,并在它们之间使用项,但不是最后一项(例如std :: ostream_iterator会生成)。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2015年12月11日在16:33

\ $ \ begingroup \ $
@JerryCoffin不,我知道。但是,您也没有任何限制地支持infix_ostream_iterator {std :: cout},我建议您不这样做。
\ $ \ endgroup \ $
–巴里
2015年12月11日在16:40

\ $ \ begingroup \ $
哦,我知道了-是的,当您没有定界符时,它与普通的ostream_iterartor之间没有真正的区别。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2015年12月11日在16:49