manipolatore personalizzato per C ++ iostream
Domanda
Mi piacerebbe realizzare un manipolatore personalizzato per ostream fare qualche manipolazione sulla prossima voce di essere inserito nel flusso. Per esempio, diciamo che ho un manipolatore personalizzato citazione :
std::ostringstream os;
std::string name("Joe");
os << "SELECT * FROM customers WHERE name = " << quote << name;
Il manipolatore citazione citeremo nome per la produzione di:
SELECT * FROM customers WHERE name = 'Joe'
Come posso fare per realizzare questo? Grazie.
Soluzione
E 'particolarmente difficile per aggiungere un manipolatore ad un C ++ flusso, come si ha alcun controllo su come viene utilizzato il manipolatore. Si può infondere un nuovo locale in un flusso, che ha un aspetto installato che controlla come i numeri sono stampati - ma non come stringhe sono uscita. E allora il problema sarebbe ancora come memorizzare lo stato citando in modo sicuro nel flusso.
Le stringhe sono uscita utilizzando un operatore definito nel namespace std
. Se si desidera cambiare il modo in cui quelli sono stampati, 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;
}
Il che sarebbe adatto per essere utilizzato per ostream
. Se si vuole generalizzare, si può rendere un modello troppo e anche accettare basic_stream
invece di string
pianura. Ha diversi comportamenti a manipolatori standard in alcuni casi. Perché funziona restituendo l'oggetto proxy, non funzionerà per casi come
std::cout << quoting::quote;
std::cout << "hello";
Altri suggerimenti
Prova questo:
#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;
}
[EDIT: "semantica manipolatore veri" (vale a dire Stati citando persistente) potrebbe essere ottenuta anche da involucro un std::ostream
piuttosto che ne derivano, come notato da Benoit nei commenti. ]
Per quanto a mia conoscenza, questo non può essere fatto direttamente senza né derivare una nuova classe da std::ostream
o simili, o confezionare una classe in un'altra classe che inoltra la maggior parte dei metodi per il suo oggetto std::ostream
contenute. Questo perché, per l'esempio di codice che fornisci a lavorare, è necessario modificare in qualche modo il comportamento di std::ostream& operator<<(std::ostream&, std::string const&)
, che è definito da qualche parte nella gerarchia iostreams (o forse ovunque std::string
è definito). Sarà inoltre necessario utilizzare le strutture (un po 'brutto) in ios_base
per registrare un flag booleano tenendo lo stato citando corrente. Look up ios_base::xalloc()
, ios_base::iword()
e ios_base::pword()
per scoprire come fare.
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, naturalmente).
Questa sintassi ha il vantaggio che, citando non è persistente, il che significa che non si può "fuoriuscire" quando una funzione imposta la formattazione quote
bandiera e si dimentica di impostare di nuovo al suo valore originale.
o semplicemente usare OTL che sostanzialmente implementa già un'interfaccia flusso per SQL in modo molto simile al tuo esempio.