73 votes

Dois-je utiliser printf dans mon code C++ ?

J'utilise généralement cout et cerr pour écrire du texte dans la console. Cependant, je trouve parfois plus facile d'utiliser la bonne vieille fonction printf déclaration. Je l'utilise lorsque j'ai besoin de formater la sortie.

Voici un exemple d'utilisation :

// Lets assume that I'm printing coordinates... 
printf("(%d,%d)\n", x, y);

// To do the same thing as above using cout....
cout << "(" << x << "," << y << ")" << endl;

Je sais que je peux formater la sortie en utilisant cout mais je sais déjà comment utiliser le printf . Y a-t-il une raison pour laquelle je ne devrais pas utiliser l'option printf déclaration ?

3 votes

Pour les E/S de la console es macht nichts (cela n'a pas d'importance). Dans l'ensemble, printf n'est pas compatible avec les flux C++. Les flux C++ vous permettent de convertir facilement la sortie de votre console vers un fichier. (Bien que vous puissiez faire la même chose avec fprintf ).

1 votes

pourquoi ne pas utiliser sprintf + cout alors ?

14 votes

Mote que vos deux lignes ne sont pas strictement équivalentes. endl permet également de vider le flux, comme si vous aviez écrit printf("(%d,%d)\n", x, y); fflush(stdout); Cela peut ajouter un grand une baisse de performance si elle est exécutée de manière répétée dans une boucle. Pour obtenir un véritable équivalent de votre instruction printf en C++, vous devez écrire cout << "(" << x << "," << y << ")\n";

75voto

Norman Ramsey Points 115730

Mes étudiants, qui apprennent cin et cout d'abord, puis apprendre printf plus tard, une majorité écrasante préfère printf (ou plus généralement fprintf ). J'ai moi-même trouvé le printf est suffisamment lisible pour que je le transpose dans d'autres langages de programmation. Il en est de même pour Olivier Danvy qui l'a même rendu sûr du point de vue du type.

Si vous disposez d'un compilateur capable de vérifier le type des appels à printf je ne vois aucune raison de ne pas utiliser fprintf et amis en C++.

Avis de non-responsabilité : Je suis un piètre programmeur C++.

17 votes

J'utilise la fonction String.format souvent. En C++, j'utilise beaucoup Boost.Format, qui est facile à utiliser mais aussi un peu printf -compatible.

5 votes

Le manque de sécurité de type de *printf est atténué mais pas éliminé par les compilateurs qui vérifient ces appels, puisque l'utilisation de variables comme chaînes de format est un cas d'utilisation parfaitement valide. par exemple : i18n. Cette fonction peut exploser de tellement de façons que ce n'est même pas drôle. Je ne l'utilise tout simplement plus. Nous avons accès à de très bons formateurs tels que boost::format ou Qt::arg.

0 votes

@rpg : Je pense que le compromis est entre la lisibilité et la sécurité des types. Je pense qu'il est raisonnable que des applications différentes fassent des compromis différents. Une fois que vous êtes dans l'i18n, la lisibilité est déjà à mi-chemin de la fenêtre de toute façon, et je suis d'accord que la sécurité de type l'emporte sur printf dans cette situation. Il est intéressant de noter que la façon dont boost::format est quelque peu similaire à la façon dont le code ML d'Olivier Danvy fonctionne.

49voto

R Samuel Klatchko Points 44549

Si vous espérez un jour i18n votre programme, restez loin des iostreams. Le problème est qu'il peut être impossible de localiser correctement vos chaînes de caractères si la phrase est composée de multiples fragments comme c'est le cas avec iostream.

Outre le problème des fragments de message, vous avez également un problème de commande. Considérons un rapport qui imprime le nom d'un étudiant et sa moyenne :

std::cout << name << " has a GPA of " << gpa << std::endl;

Lorsque vous traduisez cela dans une autre langue, il se peut que la grammaire de l'autre langue vous oblige à faire figurer le GPA avant le nom. A ce jour, iostreams n'a aucun moyen de réorganiser les valeurs interpolées.

Si vous voulez le meilleur des deux mondes (sécurité de type et possibilité d'i18n), utilisez Boost.Format .

5 votes

La possibilité de spécifier les paramètres de formatage par position dans boost::format() est excellente pour la localisation.

11 votes

Mais boost::format vous offre des commodités telles que la sécurité du type. printf explose juste.

2 votes

Bonne réponse. Je veux juste ajouter qu'il existe des alternatives au format Boost qui sont plusieurs fois plus rapides tout en offrant le même niveau de sécurité : github.com/vitaut/format et github.com/c42f/tinyformat

21voto

Igor Zevaka Points 32586

J'utilise printf parce que je déteste l'affreux <<cout<< la syntaxe.

3 votes

quelle meilleure raison y a-t-il ? :P

21voto

phresnel Points 20082

Adaptabilité

Toute tentative de printf un non-POD entraîne un comportement non défini :

struct Foo { 
    virtual ~Foo() {}
    operator float() const { return 0.f; }
};

printf ("%f", Foo());

std::string foo;
printf ("%s", foo);

Les appels printf ci-dessus produisent un comportement non défini. Votre compilateur peut en effet vous avertir, mais ces avertissements ne sont pas requis par les normes et ne sont pas possibles pour les chaînes de format connues uniquement au moment de l'exécution.

IO-Streams :

std::cout << Foo();
std::string foo;
std::cout << foo;

Jugez vous-même.

Extensibilité

struct Person {
    string first_name;
    string second_name;
};
std::ostream& operator<< (std::ostream &os, Person const& p) {
    return os << p.first_name << ", " << p.second_name;
}

cout << p;
cout << p;
some_file << p;

C :

// inline everywhere
printf ("%s, %s", p.first_name, p.second_name);
printf ("%s, %s", p.first_name, p.second_name);
fprintf (some_file, "%s, %s", p.first_name, p.second_name);

ou :

// re-usable (not common in my experience)
int person_fprint(FILE *f, const Person *p) {
    return fprintf(f, "%s, %s", p->first_name, p->second_name);
}
int person_print(const Person *p) {
    return person_fprint(stdout, p);
}

Person p;
....
person_print(&p);

Notez que vous devez prendre soin d'utiliser les bons arguments/signatures d'appel en C (ex. person_fprint(stderr, ... , person_fprint(myfile, ... ), alors qu'en C++, le " FILE -argument" est automatiquement "dérivé" de l'expression. Un équivalent plus exact de cette dérivation est en fait plus proche de ceci :

FILE *fout = stdout;
...
fprintf(fout, "Hello World!\n");
person_fprint(fout, ...);
fprintf(fout, "\n");

I18N

Nous réutilisons notre définition de la personne :

~~cout << boost::format("Hello %1%") % p; cout << boost::format("Na %1%, sei gegrüßt!") % p;

printf ("Hello %1$s, %2$s", p.first_name.c_str(), p.second_name.c_str()); 
printf ("Na %1$s, %2$s, sei gegrüßt!", 
        p.first_name.c_str(), p.second_name.c_str());~~ 

Jugez vous-même.

Je trouve cela moins pertinent à partir d'aujourd'hui (2017). Peut-être que c'est juste une intuition, mais I18N n'est pas quelque chose qui est fait sur une base quotidienne par votre programmeur C ou C++ moyen. De plus, c'est une douleur dans l'a...natomie de toute façon.

Performance

  1. Avez-vous mesuré l'importance réelle des performances de printf ? Vos applications goulot d'étranglement sont-elles sérieusement si paresseuses que la sortie des résultats de calcul constitue un goulot d'étranglement ? Êtes-vous sûr d'avoir besoin de C++ ?
  2. La pénalité de performance redoutée est destinée à satisfaire ceux d'entre vous qui veulent utiliser un mélange de printf et de cout. C'est une fonctionnalité, pas un bug !

Si vous utilisez systématiquement iostreams, vous pouvez

std::ios::sync_with_stdio(false);

et bénéficier d'un temps d'exécution égal avec un bon compilateur :

#include <cstdio>
#include <iostream>
#include <ctime>
#include <fstream>

void ios_test (int n) {
    for (int i=0; i<n; ++i) {
        std::cout << "foobarfrob" << i;
    }
}

void c_test (int n) {
    for (int i=0; i<n; ++i) {
        printf ("foobarfrob%d", i);
    }
}

int main () {
    const clock_t a_start = clock();
    ios_test (10024*1024);
    const double a = (clock() - a_start) / double(CLOCKS_PER_SEC);

    const clock_t p_start = clock();
    c_test (10024*1024);
    const double p = (clock() - p_start) / double(CLOCKS_PER_SEC);

    std::ios::sync_with_stdio(false);
    const clock_t b_start = clock();
    ios_test (10024*1024);
    const double b = (clock() - b_start) / double(CLOCKS_PER_SEC);

    std::ofstream res ("RESULTS");
    res << "C ..............: " << p << " sec\n"
        << "C++, sync with C: " << a << " sec\n"
        << "C++, non-sync ..: " << b << " sec\n";
}

Résultats ( g++ -O3 synced-unsynced-printf.cc , ./a.out > /dev/null , cat RESULTS ) :

C ..............: 1.1 sec
C++, sync with C: 1.76 sec
C++, non-sync ..: 1.01 sec

Jugez ... vous-même.

Non. Vous ne m'interdirez pas mon printf.

Vous pouvez créer un printf sûr pour les types et respectueux de la norme I18N en C++11, grâce aux modèles variadiques. Et vous pourrez les rendre très, très performants en utilisant des littéraux définis par l'utilisateur, c'est-à-dire qu'il sera possible d'écrire une incarnation entièrement statique.

J'ai une preuve de concept . À l'époque, le support de C++11 n'était pas aussi mature qu'aujourd'hui, mais vous pouvez vous faire une idée.

Adaptabilité temporelle

// foo.h
...
struct Frob {
    unsigned int x;
};
...

// alpha.cpp
... printf ("%u", frob.x); ...

// bravo.cpp
... printf ("%u", frob.x); ...

// charlie.cpp
... printf ("%u", frob.x); ...

// delta.cpp
... printf ("%u", frob.x); ...

Plus tard, vos données deviennent si volumineuses que vous devez faire

// foo.h
...
    unsigned long long x;
...

C'est un exercice intéressant de maintenir cela et de le faire sans bogues. Surtout quand d'autres projets, non couplés, utilisent foo.h .

Autre.

  • Bug Potentiel : Il y a beaucoup d'espace pour commettre des erreurs avec printf, surtout lorsque vous jetez des chaînes de base d'entrée utilisateur dans le mélange (pensez à votre équipe I18N). Vous devez prendre soin d'échapper correctement chacune de ces chaînes de format, vous devez être sûr de passer les bons arguments, etc. etc.

  • Les flux d'entrées/sorties rendent mon binaire plus grand. : Si c'est un problème plus important que la maintenabilité, la qualité du code, la réutilisabilité, alors (après avoir vérifié le problème !) utilisez printf.

2 votes

Les compilateurs modernes diagnostiquent les disparités entre les spécifications de format et les arguments.

1 votes

merci - c'est une très bonne explication ! @vitaut ils ne peuvent pas en général. par exemple, si la spécification de format elle-même n'est pas une chaîne littérale constante mais provient d'un fichier de configuration. ce n'est pas un exemple hypothétique, dans la plupart des logiciels commerciaux, pour 18n et pour maintenir la cohérence de la communication, la plupart des chaînes ne sont pas des littéraux en place mais compartimentées pas toujours comme des littéraux.

1 votes

@agksmehx : Très bel exemple pour contrer la fiabilité de la vérification des chaînes de format.

19voto

janm Points 9310

Utilisez boost::format. Vous obtenez la sécurité des types, le support de std::string, une interface similaire à printf, la possibilité d'utiliser cout, et beaucoup d'autres bonnes choses. Vous ne reviendrez pas en arrière.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X