web-dev-qa-db-fra.com

Utilisation de sprintf avec std :: string en C++

J'utilise la fonction sprintf en C++ 11, de la manière suivante:

std::string toString()
{
    std::string output;
    uint32_t strSize=512;
    do
    {
        output.reserve(strSize);
        int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u",
            INDEX_RECORD_TYPE_SERIALIZATION_HEADER,
            FORAMT_VERSION,
            contentType,
            contentFormatVersion,
            magic,
            segmentId);

        strSize *= 2;
    } while (ret < 0);

    return output;
}

Y a-t-il un meilleur moyen de le faire que de vérifier chaque fois si l'espace réservé était suffisant? Pour la possibilité future d'ajouter plus de choses.

12
daniel the man

Votre construction - write dans le tampon reçu de c_str() - est un comportement non défini , même si vous avez préalablement vérifié la capacité de la chaîne. (La valeur de retour est un pointeur sur const char et la fonction elle-même marquée const pour une raison.)

Ne mélangez pas C et C++, spécialement, pas pour écrire dans la représentation d'objet interne. (Cela rompt une OOP très basique.) Utilisez C++, pour la sécurité de type et ne courez pas dans des incompatibilités spécificateur/paramètre de conversion, si rien d'autre.

std::ostringstream s;
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
  << " Version=" << FORMAT_VERSION
  // ...and so on...
  ;
std::string output = s.str();

Alternative:

std::string output = "Type=" + std::to_string( INDEX_RECORD_TYPE_SERIALIZATION_HEADER )
                   + " Version=" + std::to_string( FORMAT_VERSION )
                   // ...and so on...
                   ;
14
DevSolar

Les modèles C++ montrés dans les autres réponses sont plus agréables, mais pour être complet, voici une manière correcte avec sprintf:

auto format = "your %x format %d string %s";
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format, /* Arguments go here*/);

Faire attention à

  • Vous devez resize votre chaîne. reserve ne modifie pas la taille du tampon. Dans mon exemple, je construis directement une chaîne correctement dimensionnée.
  • c_str() renvoie un const char*. Vous ne pouvez pas le transmettre à sprintf.
  • std::string Il n'était pas garanti que la mémoire tampon soit contiguë avant C++ 11 et cela repose sur cette garantie. Si vous devez prendre en charge des plates-formes exotiques conformes à std::string et conformes à la version antérieure à C++ 11, il est probablement préférable de commencer par sprinting dans std::vector<char> puis de copier le vecteur dans la chaîne.
  • Cela ne fonctionne que si les arguments ne sont pas modifiés entre le calcul de la taille et le formatage; utilisez soit des copies locales de variables, soit des primitives de synchronisation de thread pour un code multithread.
8
eerorika

Nous pouvons mélanger le code à partir d'ici https://stackoverflow.com/a/36909699/2667451 et ici https://stackoverflow.com/a/7257307 et le résultat sera comme ça:

template <typename ...Args>
std::string stringWithFormat(const std::string& format, Args && ...args)
{
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...);
    std::string output(size + 1, '\0');
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...);
    return output;
}
1
zergeny

Votre code est faux. reserve alloue de la mémoire pour la chaîne, mais ne modifie pas sa taille. L'écriture dans le tampon renvoyé par c_str ne modifie pas non plus sa taille. Donc, la chaîne croit toujours que sa taille est 0 et que vous venez d'écrire quelque chose dans l'espace inutilisé du tampon de la chaîne. (Probablement. Techniquement, le code a un comportement indéfini, car l'écriture dans c_str est indéfinie, donc tout peut arriver).

Ce que vous voulez vraiment faire, c'est oublier sprintf et des fonctions similaires à celles du style C et utiliser la méthode de formatage de chaîne C++ - flux de chaînes:

std::ostringstream ss;
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
   << " Version=" << FORAMT_VERSION
   << /* ... the rest ... */;
return ss.str();
1
Angew

Oui il y a!

En C, la meilleure solution consiste à associer un fichier au périphérique null et à créer une variable factice printf de la sortie souhaitée, pour savoir combien d’espace prendrait l’impression. Allouez ensuite le tampon approprié et sprintf les mêmes données.

En C++, vous pouvez également associer le flux de sortie à un périphérique null et tester le nombre de caractères imprimés avec std :: ostream :: tellp . Cependant, utiliser ostringstream est une solution plus efficace - voir les réponses de DevSolar ou Angew.

0
CiaPan