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?
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
}
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>;
+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:
#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
parstd::wstring
, oustd::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!
#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
mise à jour: face à l'aversion des gens pour cette réponse, j'ai pensé que je pourrais faire un montage et expliquer.
-
Non, il n'y a aucun moyen d'éviter une copie de chaîne (stringbuf a la même interface)
-
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:
-
développement d'un composant logiciel (assez complexe, quand on tient compte de la gestion locale).
-
souffre de la perte de flexibilité d'avoir un streambuf qui ne supporte que les opérations de sortie.
-
pose de mines terrestres pour les futurs promoteurs.