English 中文(简体)
C++ iostream的自定义操作器
原标题:
  • 时间:2009-02-11 04:34:32
  •  标签:

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

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

操纵者“引语”将引用<姓名>来产生:

SELECT * FROM customers WHERE name =  Joe 

How do I go about accomplishing that? Thanks.

最佳回答

将其翻译为中文:将操纵符添加到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。如果想要泛化,还可以将其制作为模板,并接受basic_stream而不是普通的string。在某些情况下,它具有与标准操作符不同的行为。因为它通过返回代理对象来工作,所以它不适用于像...这样的情况。

std::cout << quoting::quote; 
std::cout << "hello";
问题回答

尝试一下:

#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;
}

[编辑:如评论中所提到,《真正的操作语义》(即持久引用状态)也可以通过std::ostream包装来实现,而不是从中派生出来。]

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

然而,如果您愿意使用以下语法:

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

这可以通过使用全局函数(当然是在适当的命名空间中)非常简单地实现。

这种语法的优点在于引号不是持久的,这意味着当函数设置 quote 格式标志并忘记将其恢复到原始值时,它不能“泄漏出去”。

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





相关问题