Déplacer la chaîne d'un std::ostringstream

si je construis une chaîne faite d'une liste de valeurs de virgule flottante séparées par des espaces en utilisant std::ostringstream :

std::ostringstream ss;
unsigned int s = floatData.size();
for(unsigned int i=0;i<s;i++)
{
    ss << floatData[i] << " ";
}

puis j'obtiens le résultat dans un std::string :

std::string textValues(ss.str());

cependant, cela provoquera une copie profonde inutile du contenu de la chaîne, car ss ne sera plus utilisé.

y a-t-il un moyen de construire la chaîne sans copier tout le contenu?

24
demandé sur Baum mit Augen 2014-10-09 01:11:23

4 réponses

std::ostringstream n'offre pas d'interface publique pour accéder à son tampon en mémoire à moins qu'il ne supporte pas pubsetbuf (mais même alors votre tampon est de taille fixe, voir exemple de référence CPP )

si vous voulez torturer certains flux de cordes, vous pouvez accéder au tampon en utilisant l'interface protégée:

#include <iostream>
#include <sstream>
#include <vector>

struct my_stringbuf : std::stringbuf {
    const char* my_str() const { return pbase(); } // pptr might be useful too
};

int main()
{
    std::vector<float> v = {1.1, -3.4, 1/7.0};
    my_stringbuf buf;
    std::ostream ss(&buf);
    for(unsigned int i=0; i < v.size(); ++i)
        ss << v[i] << ' ';
    ss << std::ends;
    std::cout << buf.my_str() << '\n';
}

le moyen standard C++ d'accéder directement à un tampon de flux de sortie à redimensionnement automatique est offert par std::ostrstream , déprécié en C++98, mais toujours standard C++14 et comptage.

#include <iostream>
#include <strstream>
#include <vector>

int main()
{
    std::vector<float> v = {1.1, -3.4, 1/7.0};
    std::ostrstream ss;
    for(unsigned int i=0; i < v.size(); ++i)
        ss << v[i] << ' ';
    ss << std::ends;
    const char* buffer = ss.str(); // direct access!
    std::cout << buffer << '\n';
    ss.freeze(false); // abomination
}

cependant, je pense que la solution la plus propre (et la plus rapide) est boost.karma

#include <iostream>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;
int main()
{
    std::vector<float> v = {1.1, -3.4, 1/7.0};
    std::string s;
    karma::generate(back_inserter(s), karma::double_ % ' ', v);
    std::cout << s << '\n'; // here's your string
}
11
répondu Cubbi 2014-10-10 03:42:56

j'ai implémenté la classe" outstringstream", qui je crois fait exactement ce dont vous avez besoin (voir la méthode take_str ()). J'ai partiellement utilisé le code de: Qu'est-ce qui ne va pas avec mon implémentation de overflow()?

#include <ostream>

template <typename char_type>
class basic_outstringstream : private std::basic_streambuf<char_type, std::char_traits<char_type>>,
                              public std::basic_ostream<char_type, std::char_traits<char_type>>
{
    using traits_type = std::char_traits<char_type>;
    using base_buf_type = std::basic_streambuf<char_type, traits_type>;
    using base_stream_type = std::basic_ostream<char_type, traits_type>;
    using int_type = typename base_buf_type::int_type;

    std::basic_string<char_type> m_str;

    int_type overflow(int_type ch) override
    {
        if (traits_type::eq_int_type(ch, traits_type::eof()))
            return traits_type::not_eof(ch);

        if (m_str.empty())
            m_str.resize(1);
        else
            m_str.resize(m_str.size() * 2);

        const std::ptrdiff_t diff = this->pptr() - this->pbase();
        this->setp(&m_str.front(), &m_str.back());

        this->pbump(diff);
        *this->pptr() = traits_type::to_char_type(ch);
        this->pbump(1);

        return traits_type::not_eof(traits_type::to_int_type(*this->pptr()));
    }

    void init()
    {
        this->setp(&m_str.front(), &m_str.back());

        const std::size_t size = m_str.size();
        if (size)
        {
            memcpy(this->pptr(), &m_str.front(), size);
            this->pbump(size);
        }
    }

public:

    explicit basic_outstringstream(std::size_t reserveSize = 8)
        : base_stream_type(this)
    {
        m_str.reserve(reserveSize);
        init();
    }

    explicit basic_outstringstream(std::basic_string<char_type>&& str)
        : base_stream_type(this), m_str(std::move(str))
    {
        init();
    }

    explicit basic_outstringstream(const std::basic_string<char_type>& str)
        : base_stream_type(this), m_str(str)
    {
        init();
    }

    const std::basic_string<char_type>& str() const
    {
        return m_str;
    }

    std::basic_string<char_type>&& take_str()
    {
        return std::move(m_str);
    }

    void clear()
    {
        m_str.clear();
        init();
    }
};

using outstringstream = basic_outstringstream<char>;
using woutstringstream = basic_outstringstream<wchar_t>;
3
répondu Kuba S. 2017-05-23 12:10:08

+1 pour le karma Boost par @Cubbi et la suggestion de "créez votre propre type streambuf -dervied qui ne fait pas de copie, et donnez cela au constructeur d'un basic_istream<> ." .

une réponse plus générique, cependant, est manquante, et se situe entre ces deux. Il utilise Boost iostream:

using string_buf = bio::stream_buffer<bio::back_insert_device<std::string> >;

voici un programme de démonstration:

En Direct Sur Coliru

#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream_buffer.hpp>

namespace bio = boost::iostreams;

using string_buf = bio::stream_buffer<bio::back_insert_device<std::string> >;

// any code that uses ostream
void foo(std::ostream& os) {
    os << "Hello world " 
       << std::hex << std::showbase << 42
       << " " << std::boolalpha << (1==1) << "\n";
}

#include <iostream>

int main() {
    std::string output;
    output.reserve(100); // optionally optimize if you know roughly how large output is gonna, or know what minimal size it will require

    {
        string_buf buf(output);
        std::ostream os(&buf);
        foo(os);
    }

    std::cout << "Output contains: " << output;
}

notez que vous pouvez trivialement remplacer le std::string par std::wstring , ou std::vector<char> etc.

encore mieux, vous pouvez l'utiliser avec le dispositif array_sink et avoir un tampon fixe-size . De cette façon, vous pouvez éviter toute tampon allocation que ce soit avec votre code iostream!

En Direct Sur Coliru

#include <boost/iostreams/device/array.hpp>

using array_buf = bio::stream_buffer<bio::basic_array<char>>;

// ...

int main() {
    char output[100] = {0};

    {
        array_buf buf(output);
        std::ostream os(&buf);
        foo(os);
    }

    std::cout << "Output contains: " << output;
}

les deux programmes imprimer:

Output contains: Hello world 0x2a true
3
répondu sehe 2017-05-23 12:17:55

mise à jour: face à l'aversion des gens pour cette réponse, j'ai pensé que je pourrais faire un montage et expliquer.

  1. Non, il n'y a aucun moyen d'éviter une copie de chaîne (stringbuf a la même interface)

  2. cela n'aura jamais d'importance. C'est en fait plus efficace de cette façon. (Je vais essayer de vous l'expliquer)

Imagine écrire une version de stringbuf qui garde un parfait, mobile std::string disponible à tout moment. (J'ai essayé).

ajouter des caractères est facile - nous utilisons simplement push_back sur la chaîne sous-jacente.

OK, mais qu'en est-il de la suppression des caractères (lecture à partir du buffer)? On va devoir déplacer un pointeur pour tenir compte des personnages qu'on a retirés, tout va bien.

Cependant, nous avons un problème - le contrat que nous gardons dit que nous aurons toujours un std::string disponible.

donc chaque fois que nous supprimons des caractères du flux, nous aurons besoin de erase les de la chaîne sous-jacente. Cela signifie qu'il faut mélanger tous les caractères restants.( memmove / memcpy ). Parce que ce contrat doit être maintenu chaque fois que le flux de contrôle quitte notre implémentation privée, cela signifie en pratique avoir à effacer les caractères de la chaîne chaque fois que nous appelons getc ou gets sur la chaîne de la mémoire tampon. Cela se traduit par un appel à effacer sur chaque opération << sur le flux.

alors bien sûr il y a le problème de l'implémentation du tampon pushback. Si vous repoussez les caractères dans la chaîne sous - jacente, vous devez les insert à la position 0-mélangeant le tampon entier vers le haut.

le long et court de celui-ci est que vous pouvez écrire un tampon de jet d'eau-seulement pour la construction un std::string . Vous aurez toujours besoin de gérer toutes les réallocations à mesure que le tampon sous-jacent croît, donc à la fin vous pourrez enregistrer exactement une copie de chaîne de caractères. Donc peut-être que nous allons de 4 copies de chaîne (et appels à malloc/free) à 3, ou 3 à 2.

vous aurez également besoin de traiter le problème que l'interface de streambuf n'est pas divisé en istreambuf et ostreambuf . Cela signifie que vous avez encore à offrir l'interface d'entrée et soit jeter des exceptions ou d'affirmer si quelqu'un utiliser. Cela revient à mentir aux utilisateurs - nous avons échoué à mettre en œuvre une interface attendue.

pour cette petite amélioration de la performance, nous devons payer le coût de:

  1. développement d'un composant logiciel (assez complexe, quand on tient compte de la gestion locale).

  2. souffre de la perte de flexibilité d'avoir un streambuf qui ne supporte que les opérations de sortie.

  3. pose de mines terrestres pour les futurs promoteurs.

0
répondu Richard Hodges 2016-07-01 09:11:38