2009-02-11 2 views
11

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

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

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

SELECT * FROM customers WHERE name = 'Joe' 

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

+1

Это уже почти три года, но вы знаете, что это уязвимо для SQL-инъекции, верно? :) Надеюсь, это был просто произвольный пример! – Geoff

+0

@ Geoff (Еще три года спустя) Это полностью зависит от реализации «quote». Это может быть непроницаемо для SQL-инъекций довольно тривиально. Я бы не выбрал этот конкретный интерфейс, но он не является априорным небезопасным. –

ответ

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

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

Умный ответ, но вы должны упомянуть, что ваша цитата :: quote имеет другую семантику, чем все другие манипуляторы - cout << quote << "X"; цитаты, но 'cout << quote; cout << "X"; Нет, вы можете даже назвать эту функцию, однако она несовместима с другими манипуляторами. –

+0

j_random_hacker. Хмм, я вижу. Ну, я подумал о том, чтобы сбросить «чувство» в «внешний вид» :). Теперь вы дали мне хорошую аргумент для этого. спасибо :) –

+0

Гораздо понятнее. Хороший вопрос о локалях для изменения числового вывода BTW - это полезный «маршрут доступа», о котором я забыл. :) –

6

[EDIT:. «True семантика манипулятора» (то есть упорная со ссылкой на состояние) также может быть достигнуто за счет упаковка в std::ostream, а не вытекающие из нее, как было отмечено Benoit в комментарии]

Насколько я знаю, это невозможно сделать непосредственно, не вызывая новый класс от 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 и забывает вернуть его к исходному значению.

+0

Я не могу примирить этот ответ с двумя другими ответами, которые, похоже, предполагают, что это можно сделать. Что правильно? –

+0

Это можно сделать, как сказано в других ответах. Единственная ошибка в текущем ответе заключается в том, что это можно сделать «без вывода». Но, очевидно, существует потребность в новом классе и перегруженном шаблоном оператора <<. –

+0

@ 1800: умные решения ibb и Martin York имеют разную семантику, чем все существующие манипуляторы iostreams - они будут приводиться только до конца текущего утверждения. Пожалуйста, см. Мои комментарии к их ответам. –

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; 
} 
+0

Еще один умный ответ, однако вы должны упомянуть, что ваш манипулятор Quote имеет необычную семантику - еще раз он цитирует «до конца утверждения». Это нормально, но не соответствует поведению любого другого манипулятора iostreams. –

+0

@ j_random_hacker: Нет, он цитирует следующий объект, который похож, что он помещен в поток, и возвращает поток для последующих объектов. Таким образом, симантика, как и следовало ожидать. –

+1

@Martin: Упс, вы правы только о цитировании следующего пункта. Но все же дело в том, что поведение отличается от других манипуляторов следующего типа (например, setw): 'cout << Quote (' ''); cout << "plop"; не цитирует «plop» .Несмотря на то, что это плохо (я на самом деле думаю, что это безопаснее), просто разные. –

1

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

Смежные вопросы