Кастомный манипулятор для C ++ iostream - PullRequest
11 голосов
/ 11 февраля 2009

Я хотел бы реализовать пользовательский манипулятор для ostream, чтобы выполнять некоторые манипуляции со следующим элементом, вставляемым в поток. Например, скажем, у меня есть пользовательский манипулятор цитата :

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

Манипулятор цитата будет цитировать имя для производства:

SELECT * FROM customers WHERE name = 'Joe'

Как мне добиться этого? Спасибо.

Ответы [ 4 ]

18 голосов
/ 11 февраля 2009

Особенно сложно добавить манипулятор в поток 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";
7 голосов
/ 11 февраля 2009

Попробуйте это:

#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;
}
6 голосов
/ 11 февраля 2009

[РЕДАКТИРОВАТЬ: "Истинная семантика манипулятора" (т. Е. Состояние постоянного цитирования) также может быть достигнута с помощью обтекания 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 и забывает установить его обратно в исходное значение.

1 голос
/ 11 февраля 2009

Или просто используйте OTL , который в основном уже реализует потоковый интерфейс для SQL очень похоже на ваш пример.

...