11

我想为 ostream 实现一个自定义操纵器,以对插入到流中的下一个项目进行一些操作。例如,假设我有一个自定义操纵器报价

std::ostringstream os;
std::string name("Joe");
os << "SELECT * FROM customers WHERE name = " << quote << name;  

操纵器引用将引用名称来产生:

SELECT * FROM customers WHERE name = 'Joe'

我该如何实现呢?谢谢。

4

4 回答 4

18

将操纵器添加到 C++ 流中特别困难,因为无法控制操纵器的使用方式。可以将新的语言环境注入到流中,该流安装了一个控制如何打印数字的方面 - 但不控制如何输出字符串。然后问题仍然是如何将引用状态安全地存储到流中。

使用命名空间中定义的运算符输出字符串std。如果你想改变它们的打印方式,同时保持操纵器的外观,你可以创建一个代理类:

namespace quoting {
struct quoting_proxy {
    explicit quoting_proxy(std::ostream & os):os(os){}

    template<typename Rhs>
    friend std::ostream & operator<<(quoting_proxy const& q, 
                                     Rhs const& rhs) {
        return q.os << rhs;
    }

    friend std::ostream & operator<<(quoting_proxy const& q, 
                                     std::string const& rhs) {
        return q.os << "'" << rhs << "'";
    }

    friend std::ostream & operator<<(quoting_proxy const& q, 
                                     char const* rhs) {
        return q.os << "'" << rhs << "'";
    }
private:
    std::ostream & os;
};

struct quoting_creator { } quote;
quoting_proxy operator<<(std::ostream & os, quoting_creator) {
    return quoting_proxy(os);
}
}

int main() {
    std::cout << quoting::quote << "hello" << std::endl; 
}

哪个适合用于ostream. 如果你想概括,你也可以把它做成一个模板,也可以用 acceptbasic_stream代替 plain string。在某些情况下,它与标准操纵器具有不同的行为。因为它通过返回代理对象来工作,所以它不适用于像这样的情况

std::cout << quoting::quote; 
std::cout << "hello";
于 2009-02-11T06:29:25.667 回答
7

试试这个:

#include <iostream>
#include <iomanip>

// The Object that we put on the stream.
// Pass in the character we want to 'quote' the next object with.
class Quote
{
    public:
        Quote(char x)
            :m_q(x)
        {}
    private:
        // Classes that actual does the work.
        class Quoter
        {
            public:
                Quoter(Quote const& quote,std::ostream& output)
                    :m_q(quote.m_q)
                    ,m_s(output)
                {}

                // The << operator for all types. Outputs the next object
                // to the stored stream then returns the stream. 
                template<typename T>
                std::ostream& operator<<(T const& quoted)
                {
                    return m_s << m_q << quoted << m_q;
                }

            private:
                char            m_q;
                std::ostream&   m_s;
        };
        friend Quote::Quoter operator<<(std::ostream& str,Quote const& quote);

    private:
        char    m_q;
};

// When you pass an object of type Quote to an ostream it returns
// an object of Quote::Quoter that has overloaded the << operator for
// all types. This will quote the next object and the return the stream
// to continue processing as normal.
Quote::Quoter operator<<(std::ostream& str,Quote const& quote)
{
    return Quote::Quoter(quote,str);
}


int main()
{
    std::cout << Quote('"') << "plop" << std::endl;
}
于 2009-02-11T06:24:51.420 回答
6

[编辑:正如 Benôit 在评论中指出的那样,“真正的操纵器语义”(即持续引用状态)也可以通过包装而不是从它派生来实现。 ]std::ostream

据我所知,如果不从或类似的类派生新类,或者将此类类包装在另一个将大多数方法转发到其包含的对象的类中,则无法直接完成此操作这是因为,对于您提供的代码示例,您需要以某种方式修改iostreams 层次结构中某处(或可能定义的任何位置)中定义的行为。您还需要使用(有点难看的)工具来记录一个布尔标志,该标志持有当前的引用状态。查找,并找出如何做到这一点。std::ostreamstd::ostreamstd::ostream& operator<<(std::ostream&, std::string const&)std::stringios_baseios_base::xalloc()ios_base::iword()ios_base::pword()

但是,如果您愿意使用以下语法:

os << "SELECT * FROM customers WHERE name = " << quote(name);

这可以使用全局函数(当然在适当的命名空间中)非常简单地完成。

quote这种语法的优点是引用不是持久的,这意味着当函数设置格式化标志并忘记将其设置回其原始值时,它不能“泄漏” 。

于 2009-02-11T05:03:52.597 回答
1

或者只是使用OTL,它基本上已经为 SQL 实现了一个流接口,与您的示例非常相似。

于 2009-02-11T07:28:47.477 回答