2009-02-11 9 views
11

Vorrei implementare un manipolatore personalizzato per ostream per eseguire alcune manipolazioni sull'elemento successivo inserito nello stream. Per esempio, diciamo che ho un personalizzato manipolatore citazione:Manipolatore personalizzato per C++ iostream

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

Il manipolatore citazione sarà citare nome per produrre:

SELECT * FROM customers WHERE name = 'Joe' 

Come posso fare per realizzare questo? Grazie.

+1

Questo è quasi tre anni di ritardo, ma sai che è vulnerabile all'iniezione SQL, giusto? :) Spero che questo fosse solo un esempio arbitrario! – Geoff

+0

@Geoff (Altri tre anni dopo) Ciò dipende interamente dall'implementazione di 'quote'. Può essere reso insensibile alle iniezioni SQL piuttosto banalmente. Non sceglierei questa particolare interfaccia, ma non è a priori non sicura. –

risposta

18

E 'particolarmente difficile per aggiungere un manipolatore ad un C++ flusso, come si ha alcun controllo su come viene utilizzato il manipolatore. Si può inserire una nuova localizzazione in uno stream, che ha un aspetto installato che controlla come vengono stampati i numeri - ma non come vengono prodotte le stringhe. E poi il problema sarebbe ancora come memorizzare lo stato di quotazione in modo sicuro nel flusso.

Le stringhe vengono emesse utilizzando un operatore definito nello spazio dei nomi std. Se si desidera cambiare il modo in cui questi vengono stampate, pur mantenendo l'aspetto di manipolatori, è possibile creare una classe 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; 
} 

Quale sarebbe adatto ad essere utilizzato per ostream. Se si desidera generalizzare, è possibile renderlo anche un modello e accettare anche basic_stream anziché il semplice string. In alcuni casi ha comportamenti diversi rispetto ai manipolatori standard.Perché funziona restituendo l'oggetto proxy, non funzionerà per i casi come

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

risposta intelligente, tuttavia, dovresti menzionare che la tua quoting :: quote ha semantica diversa da tutti gli altri manipolatori, in particolare, " cout << quote << "X"; ' citazioni ma 'cout << quote; cout << "X";' no. Si potrebbe anche chiamare questa funzione, tuttavia è incoerente con altri manipolatori. –

+0

j_random_hacker. hmm vedo ... ho pensato di lasciar cadere il "feel" in "look and feel" :). ora mi hai dato una buona Grazie per averlo fatto, grazie :) –

+0

Molto più chiaro ora. Buon punto sulle impostazioni locali per la modifica dell'output numerico. BTW: è un utile "percorso di accesso" di cui mi ero dimenticato :) –

6

[EDIT:. "Veri semantica manipolatore" (cioè uno stato citando persistente) potrebbe essere ottenuta anche con incarto un std::ostream piuttosto che ne deriva, come rilevato da Benôit nei commenti]

Per quanto a mia conoscenza, non è possibile eseguire direttamente senza derivare una nuova classe da std::ostream o simile, o fasciare una classe di questo tipo in un'altra classe che inoltra la maggior parte dei metodi al suo oggetto std::ostream contenuto. Questo perché, per l'esempio di codice fornito dall'utente, è necessario modificare in qualche modo il comportamento di std::ostream& operator<<(std::ostream&, std::string const&), definito da qualche parte nella gerarchia di iostreams (o eventualmente dovunque sia definito std::string). Sarà inoltre necessario utilizzare le (un po 'brutte) strutture in ios_base per registrare un flag booleano che mantiene lo stato corrente di quotazione. Cerca ios_base::xalloc(), ios_base::iword() e ios_base::pword() per scoprire come farlo.

Tuttavia, se si è disposti a utilizzare la seguente sintassi:

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

Questo può essere fatto in modo molto semplice utilizzando una funzione globale (in un dominio appropriato ovviamente).

Questa sintassi ha il vantaggio che il quoting non è persistente, ovvero non può "fuoriuscire" quando una funzione imposta il flag di formattazione quote e si dimentica di reimpostarlo sul valore originale.

+0

Non riesco a conciliare questa risposta con le altre due risposte che sembrano suggerire che si possa fare. Che è corretto? –

+0

Può essere fatto come detto nelle altre risposte. L'unico errore nella risposta attuale è che può essere fatto "senza derivare". Ma chiaramente c'è bisogno di una nuova classe e di un operatore con un modello sovraccarico <<. –

+0

@ 1800: le soluzioni intelligenti di litb e Martin York hanno una semantica diversa da tutti i manipolatori di iostream esistenti: citeranno solo fino alla fine della dichiarazione corrente. Si prega di vedere i miei commenti sulle loro risposte. –

7

Prova questa:

#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

Un'altra risposta intelligente, tuttavia dovresti menzionare che il tuo manipolatore preventivo ha una semantica insolita - ancora una volta cita "fino alla fine della dichiarazione". Che va bene, ma non è coerente con il comportamento di qualsiasi altro manipolatore di iostreams. –

+0

@ j_random_hacker: No, cita l'oggetto successivo che sembra posizionato nello stream e restituisce il flusso per gli oggetti successivi. Quindi i symantics sono come ti aspetteresti. –

+1

@ Martin: Whoops, hai ragione solo a citare il prossimo articolo. Ma è pur sempre il caso che il comportamento differisce dagli altri manipolatori del prossimo oggetto (ad esempio setw): 'cout << Quote (' "'); cout <<" plop ";' non cita "plop", non dicendo che è male (in realtà penso che sia più sicuro), semplicemente diverso –

1

o semplicemente usare OTL che sostanzialmente implementa già un'interfaccia flusso per SQL in modo molto simile al tuo esempio.

Problemi correlati