2014-04-30 9 views
9

Ho un nome std::string che voglio riempire con i dati tramite un'interfaccia std::ostream ed evitare una copia di stringa.
Un modo per farlo che fa coinvolgere un copia è per fare questo:Evitare di copiare string da ostringstream

bool f(std::string& out) 
{ 
    std::ostringstream ostr; 
    fillWithData(ostr); 
    out = ostr.str(); // 2 copies here 
    return true; 
} 

ho bisogno di passare attraverso il risultato out e non può ritornoostr.str().
Desidero evitare le copie in out = ostr.str(); poiché questa stringa può essere molto grande.

C'è un modo, forse usando rdbuf() s, per collegare il buffer std::ostream direttamente a out?

Per chiarire, io sono interessato al comportamento auto-espansione di std::string e std::ostream in modo che il chiamante non ha bisogno di conoscere la dimensione prima della chiamata.

UPDATE: Ho appena realizzato che la linea innocuo out = ostr.str(); probabilmente comporterà copie:

  1. Il primo dal str() chiamata
  2. L'altro dall'operatore std::string assegnazione.
+2

Non sembra possibile. La tua funzione 'f' potrebbe prendere un' std :: ostringstream & 'come argomento? –

+1

Il problema con ciò che si vuole fare è che lo standard C++ non richiede che il buffer interno di * std :: string_buf * sia un * std :: string * –

+0

@DidierTrosset: Non proprio. L'uso interno di 'std :: ostringstream' è un dettaglio di implementazione. –

risposta

5

Scrivi la tua stream:

#include <ostream> 

template <typename Char, typename Traits = std::char_traits<Char>> 
class BasicStringOutputBuffer : public std::basic_streambuf<Char, Traits> 
{ 
    // Types 
    // ===== 

    private: 
    typedef std::basic_streambuf<Char, Traits> Base; 

    public: 
    typedef typename Base::char_type char_type; 
    typedef typename Base::int_type int_type; 
    typedef typename Base::pos_type pos_type; 
    typedef typename Base::off_type off_type; 
    typedef typename Base::traits_type traits_type; 

    typedef typename std::basic_string<char_type> string_type; 

    // Element Access 
    // ============== 

    public: 
    const string_type& str() const { return m_str; } 
    string_type& str() { return m_str; } 

    // Stream Buffer Interface 
    // ======================= 

    protected: 
    virtual std::streamsize xsputn(const char_type* s, std::streamsize n); 
    virtual int_type overflow(int_type); 

    // Utilities 
    // ========= 

    protected: 
    int_type eof() { return traits_type::eof(); } 
    bool is_eof(int_type ch) { return ch == eof(); } 

    private: 
    string_type m_str; 
}; 

// Put Area 
// ======== 

template < typename Char, typename Traits> 
std::streamsize 
BasicStringOutputBuffer<Char, Traits>::xsputn(const char_type* s, std::streamsize n) { 
    m_str.append(s, n); 
    return n; 
} 

template < typename Char, typename Traits> 
typename BasicStringOutputBuffer<Char, Traits>::int_type 
BasicStringOutputBuffer<Char, Traits>::overflow(int_type ch) 
{ 
    if(is_eof(ch)) return eof(); 
    else { 
     char_type c = traits_type::to_char_type(ch); 
     return xsputn(&c, 1); 
    } 
} 


// BasicStringOutputStream 
//============================================================================= 

template < typename Char, typename Traits = std::char_traits<Char> > 
class BasicStringOutputStream : public std::basic_ostream<Char, Traits> 
{ 
    protected: 
    typedef std::basic_ostream<Char, Traits> Base; 

    public: 
    typedef typename Base::char_type char_type; 
    typedef typename Base::int_type int_type; 
    typedef typename Base::pos_type pos_type; 
    typedef typename Base::off_type off_type; 
    typedef typename Base::traits_type traits_type; 
    typedef typename BasicStringOutputBuffer<Char, Traits>::string_type string_type; 

    // Construction 
    // ============ 

    public: 
    BasicStringOutputStream() 
    : Base(&m_buf) 
    {} 

    // Element Access 
    // ============== 

    public: 
    const string_type& str() const { return m_buf.str(); } 
    string_type& str() { return m_buf.str(); } 

    private: 
    BasicStringOutputBuffer<Char, Traits> m_buf; 
}; 

typedef BasicStringOutputStream<char> StringOutputStream; 


// Test 
// ==== 

#include <iostream> 

int main() { 
    StringOutputStream stream; 
    stream << "The answer is " << 42; 
    std::string result; 
    result.swap(stream.str()); 
    std::cout << result << '\n'; 

} 

Nota: Si potrebbe gestire i puntatori put zona in una più sofisticata implementazione.

+0

+1 per la soluzione completa. Non aumentare gli iostreams rende il codice più corto e meno incline agli errori? –

+0

@AdiShavit Penso che boost: gli iostreams sono wrapper di convenienza che non forniscono funzionalità aggiuntive (ma potrei sbagliarmi) –

2

Spostarlo: out = std::move(ostr.str()).

+2

Sposterai una copia di una stringa (se il buffer di flusso tiene una stringa) –

+1

'str()' restituisce 'std :: string', non' std :: string const & '. Questa risposta elimina la copia che è possibile prevenire; Ovviamente nessuna copia interna può essere evitata dall'esterno. – MSalters

+0

@MSalters: +1. Ciò impedirà la copia dall'operatore di assegnazione, ma non quella da 'str()'. –

Problemi correlati