【问题标题】:Custom manipulator for C++ iostreamC++ iostream 的自定义操纵器
【发布时间】:2010-10-06 19:59:32
【问题描述】:

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

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

操纵器quote会引用name来产生:

SELECT * FROM customers WHERE name = 'Joe'

我该如何实现呢? 谢谢。

【问题讨论】:

  • 这已经晚了将近三年,但你知道这很容易受到 SQL 注入的攻击,对吧? :) 我希望这只是一个随意的例子!
  • @Geoff(又过了三年)这完全取决于quote 的实现。它可以很简单地不受 SQL 注入的影响。我不会选择这个特定的接口,但它不是先验不安全的。

标签: c++ string iostream


【解决方案1】:

将操纵器添加到 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";

【讨论】:

  • 聪明的回答 litb,但是你应该提到你的 quoting::quote 与所有其他操纵器具有不同的语义——特别是,'cout
  • j_random_hacker。嗯,我明白了。好吧,我想过在“外观”中放弃“感觉”:)。现在你给了我一个很好的论据。谢谢:)
  • 现在清楚多了。关于更改数字输出的语言环境的好处顺便说一句——这是我忘记的有用的“访问路线”。 :)
  • 我在这里犯了同样的错误,就像我对马丁的回答所做的那样——你的 quoting::quote 实际上只引用了语句中的下一个项目,而不是语句结束之前的所有项目。 (我认为这在许多方面比保持持久格式状态更安全。)
【解决方案2】:

试试这个:

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

【讨论】:

  • 另一个聪明的答案,但是你应该提到你的引用操纵器有不寻常的语义——它再次引用“直到语句结束”。这很好,但与任何其他 iostreams 操纵器的行为不一致。
  • @j_random_hacker:不,它引用看起来像是放置在流上的下一个对象,然后为后续对象返回流。所以语义如你所料。
  • @Martin:哎呀,你只引用下一项是对的。但情况仍然是,行为与其他仅下一项操作符(例如 setw)不同:'cout
【解决方案3】:

[编辑:正如 Benôit 在厘米。]

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

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

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

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

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

【讨论】:

  • 我无法将此答案与其他两个似乎表明可以完成的答案相协调。哪个是正确的?
  • 可以按照其他答案中的说明来完成。当前答案中的唯一错误是它可以“不推导”来完成。但显然需要一个新类和一个重载的模板化操作符
  • @1800:litb 和 Martin York 的巧妙解决方案与所有现有的 iostreams 操纵器具有不同的语义——它们只会引用到当前语句的结尾。请查看我的 cmets 的答案。
  • 1800,你可以添加你的操纵器。 manip 只是 ostream& doit(ostream&os) { ... } 但更复杂的是 - 如何控制输出以及如何存储您的状态。这就是让我使用另一个更简单的解决方案的原因
  • @j_random_hacker,感谢您提及 xalloc 和朋友。我不知道它们可以用于用户程序。我现在调查了一下。但我仍然认为这不是一件容易的事:) 无论如何我想不出一种方法来“挂钩”到 std:: 的 operator
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-11
    • 2010-10-22
    相关资源
    最近更新 更多