2009-02-11 15 views
11

Me gustaría implementar un manipulador personalizado para que ostream pueda manipular el siguiente elemento que se inserta en la transmisión. Por ejemplo, digamos que tengo una costumbre manipulador cita:Manipulador personalizado para C++ iostream

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

El manipulador cita citarán nombre para producir:

SELECT * FROM customers WHERE name = 'Joe' 

¿Cómo hago para lograr eso? Gracias.

+1

Esto es casi tres años demasiado tarde, pero usted sabe que es vulnerable a la inyección de SQL, ¿verdad? :) ¡Espero que esto sea solo un ejemplo arbitrario! – Geoff

+0

@Geoff (Otro tres años más tarde) Eso depende completamente de la implementación de 'quote'. Puede hacerse impermeable a las inyecciones de SQL de manera bastante trivial. No elegiría esta interfaz en particular, pero no es a priori insegura. –

Respuesta

18

Resulta especialmente difícil añadir un manipulador de flujo de un C++, ya que uno no tiene control de cómo se utiliza el manipulador. Uno puede imbuir una nueva configuración regional en una secuencia, que tiene una faceta instalada que controla cómo se imprimen los números, pero no cómo se envían las cadenas. Y entonces el problema sería cómo almacenar el estado de cotización de manera segura en la transmisión.

Las cadenas se envían utilizando un operador definido en el espacio de nombres std. Si desea cambiar la forma en que las personas se imprimen, pero manteniendo el aspecto de manipuladores, puede crear una clase de proxy:

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

que sería adecuado para ser utilizado para ostream. Si desea generalizar, también puede convertirlo en una plantilla y aceptar basic_stream en lugar de simplemente string. Tiene comportamientos diferentes a los manipuladores estándar en algunos casos.Debido a que funciona mediante la devolución del objeto proxy, no va a funcionar para casos como

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

Respuesta inteligente, sin embargo, debe mencionar que su cita de cotización tiene una semántica diferente a todos los demás manipuladores, en particular, ' cout << quote << "X"; ' citas pero 'cout << quote; cout << "X";' no. Incluso se puede llamar a esto una función, sin embargo, es inconsistente con otros manipuladores. –

+0

j_random_hacker. hmm veo. bueno, pensé en dejar caer la "sensación" en "apariencia" :). ahora me diste una buena argumento para hacerlo. Gracias :) –

+0

Mucho más claro ahora. Buen punto acerca de las configuraciones regionales para cambiar la salida numérica BTW - esa es una útil "ruta de acceso" que había olvidado. :) –

6

[EDIT:. "TRUE semántica manipulador" (es decir, un estado citando persistente) también podría lograrse mediante envolver un std::ostream en lugar de derivados de ella, como se ha señalado por Benôit en los comentarios]

Por lo que yo sé, esto no se puede hacer directamente sin derivar una nueva clase de std::ostream o similar, o envolviendo dicha clase en otra clase que reenvía la mayoría de los métodos a su objeto std::ostream contenido. Esto se debe a que, para el ejemplo de código que proporcione al trabajo, deberá modificar de alguna manera el comportamiento de std::ostream& operator<<(std::ostream&, std::string const&), que se define en algún lugar de la jerarquía iostreams (o posiblemente donde esté definido std::string). También necesitará usar las instalaciones (un tanto feas) en ios_base para registrar una bandera booleana que tenga el estado actual de comillas. Consulte ios_base::xalloc(), ios_base::iword() y ios_base::pword() para averiguar cómo hacerlo.

Sin embargo, si usted está dispuesto a utilizar la siguiente sintaxis:

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

Esto se puede hacer de manera muy sencilla utilizando una función global (en un espacio de nombre adecuado, por supuesto).

Esta sintaxis tiene la ventaja de que la cotización no es persistente, lo que significa que no puede "filtrarse" cuando una función establece el indicador de formato quote y olvida volver a establecer su valor original.

+0

No puedo conciliar esta respuesta con las otras dos respuestas que parecen sugerir que se puede hacer. ¿Cual es correcta? –

+0

Se puede hacer como se dice en las otras respuestas. El único error en la respuesta actual es que se puede hacer "sin derivación". Pero claramente existe la necesidad de una nueva clase y un operador con plantilla sobrecargado <<. –

+0

@ 1800: las soluciones ingeniosas de litb y Martin York tienen una semántica diferente a todos los manipuladores iostreams existentes; solo citarán hasta el final del enunciado actual. Por favor, mira mis comentarios sobre sus respuestas. –

7

Prueba esto:

#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

Otra respuesta inteligente, sin embargo, debe mencionar que su manipulador de presupuestos tiene una semántica inusual; una vez más, cita "hasta el final de la declaración". Lo cual está bien, pero no es coherente con el comportamiento de ningún otro manipulador iostream. –

+0

@ j_random_hacker: No, cita el siguiente objeto que parece que se coloca en la secuencia y luego devuelve la secuencia para los objetos posteriores. Entonces, la semántica es lo que cabría esperar. –

+1

@Martin: Vaya, tiene razón al solo citar el siguiente artículo. Pero sigue siendo el caso que el comportamiento difiere de otros manipuladores del siguiente elemento (por ejemplo, setw): 'cout << Quote (' "'); cout <<" plop ";' no cita "plop". No dice que eso es malo (en realidad creo que es más seguro), simplemente diferente. –

1

o simplemente utilizar OTL que básicamente ya implementa una interfaz de corriente de SQL de manera muy similar a su ejemplo.

Cuestiones relacionadas