Question

Je travaille avec un outil UNIX à code source ouvert implémenté en C ++, et je dois modifier le code pour qu'il puisse faire ce que je veux. Je voudrais faire le plus petit changement possible dans l’espoir de faire accepter mon patch en amont. Les solutions qui sont implémentées en C ++ standard et ne créent pas plus de dépendances externes sont préférées.

Voici mon problème. J'ai une classe C ++ - appelons-la & "A &"; - qui utilise actuellement fprintf () pour imprimer ses structures de données fortement formatées sur un pointeur de fichier. Dans sa fonction d'impression, il appelle également de manière récursive les fonctions d'impression définies de manière identique de plusieurs classes membres (& «B &» Est un exemple). Il existe une autre classe C dont le membre est std :: string & "; Foo &"; qui doit être défini sur les résultats print () d'une instance de A. Considérez-la comme une fonction membre to_str () pour A.

En pseudocode:

class A {
public:
  ...

  void print(FILE* f);
  B b;

  ...  
};

...

void A::print(FILE *f)
{
  std::string s = "stuff";
  fprintf(f, "some %s", s);
  b.print(f);
}

class C {
  ...
  std::string foo;
  bool set_foo(std::str);
  ...
}

...

A a = new A();
C c = new C();

...

// wish i knew how to write A's to_str()
c.set_foo(a.to_str());

Je devrais mentionner que C est assez stable, mais A et B (et le reste des personnes à la charge de A) sont dans un état de transformation, donc moins de changements de code sont nécessaires, mieux c'est. L’interface d’impression actuelle (FILE * F) doit également être préservée. J'ai envisagé plusieurs approches pour implémenter A :: to_str (), chacune présentant des avantages et des inconvénients:

  1. Modifiez les appels à fprintf () en sprintf ()

    • Je n'aurais pas à réécrire les chaînes de format
    • print () pourrait être réimplémenté comme suit: fprint (f, this.to_str ());
    • Mais il me faudrait allouer manuellement char [] s, fusionner beaucoup de chaînes c et enfin convertir le tableau de caractères en std :: string
  2. Essayez de capturer les résultats de a.print () dans un flux de chaîne

    • Je devrais convertir toutes les chaînes de format en < < format de sortie. Il y a des centaines de fprintf () s à convertir: - {
    • print () devrait être réécrit, car je ne connais aucun moyen standard de créer un flux de sortie à partir d'un descripteur de fichier UNIX (bien que ce mec dit que cela pourrait être possible ).
  3. Utilisez la chaîne de Boost bibliothèque de formats

    • Plus de dépendances externes. Beurk.
    • La syntaxe du format est suffisamment différente de printf () pour être ennuyeuse:

    printf (format_str, args) - > cout < < boost :: format (format_str)% arg1% arg2% etc

  4. Utilisez les QString :: asprintf ()

    • Une dépendance externe différente.

Alors, ai-je épuisé toutes les options possibles? Si oui, quel est selon vous mon meilleur pari? Si non, qu'est-ce que j'ai oublié?

Merci.

Était-ce utile?

La solution

J'utilise # 3: la bibliothèque de format de chaîne boost - mais je dois admettre que les différences de spécifications de format n'ont jamais posé problème

Fonctionne comme un charme pour moi - et les dépendances externes pourraient être pires (une bibliothèque très stable)

Modifié: ajout d'un exemple d'utilisation de boost :: format à la place de printf:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);

serait quelque chose comme ceci avec la bibliothèque boost :: format:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);

J'espère que cela aide à clarifier l'utilisation de boost :: format

J'ai utilisé boost :: format comme solution de remplacement sprintf / printf dans 4 ou 5 applications (écriture de chaînes formatées dans des fichiers, ou sortie personnalisée dans des fichiers journaux) sans jamais rencontrer de problèmes de différences de format. Il se peut que certains spécificateurs de format (plus ou moins obscurs) soient différemment, mais je n’ai jamais eu de problème.

En revanche, j'avais des spécifications de format que je ne pouvais pas vraiment utiliser avec les flux (autant que je me souvienne)

Autres conseils

Voici l'idiome que j'aime bien pour rendre les fonctionnalités identiques à 'sprintf', mais en renvoyant un std :: string, et immunisées contre les problèmes de débordement de tampon. Ce code fait partie d’un projet open source que je suis en train d’écrire (licence BSD), donc tout le monde se sent libre de l’utiliser comme bon vous semble.

#include <string>
#include <cstdarg>
#include <vector>
#include <string>

std::string
format (const char *fmt, ...)
{
    va_list ap;
    va_start (ap, fmt);
    std::string buf = vformat (fmt, ap);
    va_end (ap);
    return buf;
}



std::string
vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.
    size_t size = 1024;
    char buf[size];

    // Try to vsnprintf into our buffer.
    va_list apcopy;
    va_copy (apcopy, ap);
    int needed = vsnprintf (&buf[0], size, fmt, ap);
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the
    // buffer.  On Linux & OSX, it returns the length it would have needed.

    if (needed <= size && needed >= 0) {
        // It fit fine the first time, we're done.
        return std::string (&buf[0]);
    } else {
        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So do a malloc of the right size and try again.
        // This doesn't happen very often if we chose our initial size
        // well.
        std::vector <char> buf;
        size = needed;
        buf.resize (size);
        needed = vsnprintf (&buf[0], size, fmt, apcopy);
        return std::string (&buf[0]);
    }
}

EDIT: quand j'ai écrit ce code, je ne savais pas que cela nécessitait la conformité C99 et que Windows (ainsi que l'ancienne glibc) avait un comportement vsnprintf différent, dans lequel il renvoie -1 en cas d'échec, plutôt qu'une mesure définitive de combien d'espace est nécessaire. Voici mon code révisé, tout le monde pourrait le consulter et si vous pensez que tout va bien, je le modifierai à nouveau pour en faire le seul coût indiqué:

std::string
Strutil::vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.  Be prepared to allocate dynamically if it doesn't fit.
    size_t size = 1024;
    char stackbuf[1024];
    std::vector<char> dynamicbuf;
    char *buf = &stackbuf[0];
    va_list ap_copy;

    while (1) {
        // Try to vsnprintf into our buffer.
        va_copy(ap_copy, ap);
        int needed = vsnprintf (buf, size, fmt, ap);
        va_end(ap_copy);

        // NB. C99 (which modern Linux and OS X follow) says vsnprintf
        // failure returns the length it would have needed.  But older
        // glibc and current Windows return -1 for failure, i.e., not
        // telling us how much was needed.

        if (needed <= (int)size && needed >= 0) {
            // It fit fine so we're done.
            return std::string (buf, (size_t) needed);
        }

        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So try again using a dynamic buffer.  This
        // doesn't happen very often if we chose our initial size well.
        size = (needed > 0) ? (needed+1) : (size*2);
        dynamicbuf.resize (size);
        buf = &dynamicbuf[0];
    }
}

Vous pouvez utiliser std :: string et iostreams avec un formatage, tel que l'appel setw () et d'autres dans iomanip

Ce qui suit pourrait constituer une solution alternative:

void A::printto(ostream outputstream) {
    char buffer[100];
    string s = "stuff";
    sprintf(buffer, "some %s", s);
    outputstream << buffer << endl;
    b.printto(outputstream);
}

(B::printto similaire) et définissez

void A::print(FILE *f) {
    printto(ofstream(f));
}

string A::to_str() {
    ostringstream os;
    printto(os);
    return os.str();
}

Bien sûr, vous devriez vraiment utiliser snprintf au lieu de sprintf pour éviter les débordements de mémoire tampon. Vous pouvez également sélectionner sélectivement les sprintfs les plus risqués en & Lt; & Lt; format, pour être plus sûr et changer le moins possible.

Vous devriez essayer le fichier d’en-tête SafeFormat de la bibliothèque Loki ( http: / /loki-lib.sourceforge.net/index.php?n=Idioms.Printf ). Elle ressemble à la bibliothèque de format de chaîne de boost, mais conserve la syntaxe des fonctions printf (...).

J'espère que cela aide!

S'agit-il de la sérialisation? Ou bien imprimer? Si c'est le cas, envisagez également boost :: serialization. Tout est question de & Quot; récursif & Quot; sérialisation des objets et des sous-objets.

La bibliothèque {fmt} fournit la fonction fmt :: sprintf qui effectue une mise en forme compatible printf (y compris des arguments de position conformément à POSIX ) et renvoie le résultat sous la forme std :: string :

std::string s = fmt::sprintf("The answer is %d.", 42);

Clause de non-responsabilité : je suis l'auteur de cette bibliothèque.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top