Подтвердить что ты не робот

Пользовательский манипулятор для С++ iostream

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

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

В цитате манипулятора будет указано имя для создания:

SELECT * FROM customers WHERE name = 'Joe'

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

4b9b3361

Ответ 1

Особенно сложно добавить манипулятор в поток С++, поскольку никто не контролирует, как используется манипулятор. Можно наполнить новый язык в потоке, который имеет фасет, который контролирует, как печатаются числа, но не как строки выводятся. И тогда проблема будет заключаться в том, как безопасно сохранить состояние кавычек в потоке.

Строки выводятся с использованием оператора, определенного в пространстве имен 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 вместо plain string. В некоторых случаях он имеет разные типы поведения для стандартных манипуляторов. Поскольку он работает, возвращая объект-прокси, он не будет работать для таких случаев, как

std::cout << quoting::quote; 
std::cout << "hello";

Ответ 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;
}

Ответ 3

[EDIT: "Семантика истинного манипулятора" (то есть состояние постоянной кавычки) также может быть достигнута путем обертывания 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 и забывает вернуть его к исходному значению.

Ответ 4

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