web-dev-qa-db-fra.com

En C ++, est-ce que je paie pour ce que je ne mange pas?

Examinons les exemples suivants du monde Hello en C et C++:

main.c

_#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}
_

main.cpp

_#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}
_

Quand je les compile dans godbolt à Assembly, la taille du code C n’est que de 9 lignes (_gcc -O3_):

_.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret
_

Mais la taille du code C++ est de 22 lignes (_g++ -O3_):

_.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit
_

... qui est beaucoup plus grande.

Il est célèbre qu'en C++, vous payez pour ce que vous mangez. Donc, dans ce cas, que vais-je payer?

167
Saher

Ce que vous payez, c'est d'appeler une bibliothèque lourde (moins lourde qu'une impression sur la console). Vous initialisez un objet ostream. Il y a des rangements cachés. Ensuite, vous appelez std::endl qui n'est pas synonyme de \n. La bibliothèque iostream vous aide à ajuster de nombreux paramètres et à alourdir la charge du processeur plutôt que du programmeur. C'est ce que vous payez.

Passons en revue le code:

.LC0:
        .string "Hello world"
main:

Initialisation d'un objet ostream + cout

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

Appelez à nouveau cout pour imprimer une nouvelle ligne et la rincer

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

Initialisation du stockage statique:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

En outre, il est essentiel de faire la distinction entre la langue et la bibliothèque.

BTW, ce n'est qu'une partie de l'histoire. Vous ne savez pas ce qui est écrit dans les fonctions que vous appelez.

59
Arash

Donc, dans ce cas, que vais-je payer?

std::cout est plus puissant et compliqué que printf. Il prend en charge des éléments tels que les paramètres régionaux, les indicateurs de mise en forme avec état, etc.

Si vous n'en avez pas besoin, utilisez std::printf ou std::puts - ils sont disponibles dans <cstdio>.


Il est célèbre qu'en C++, vous payez pour ce que vous mangez.

Je tiens également à préciser que C++ ! = La bibliothèque standard C++. La bibliothèque standard est supposée être polyvalente et "assez rapide", mais elle sera souvent plus lente qu'une mise en œuvre spécialisée de ce dont vous avez besoin.

D'autre part, le langage C++ s'efforce de rendre possible l'écriture de code sans payer de coûts cachés supplémentaires inutiles (par exemple, opt-in virtual, pas de garbage collection).

209
Vittorio Romeo

Vous ne comparez pas C et C++. Vous comparez printf et std::cout, qui sont capables de différentes choses (paramètres régionaux, formatage avec état, etc.).

Essayez d'utiliser le code suivant pour la comparaison. Godbolt génère le même assemblage pour les deux fichiers (testé avec gcc 8.2, -O3).

principal c:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

main.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}
173
pschill

Vos annonces comparent bien des pommes et des oranges, mais pas pour la raison que suggèrent la plupart des autres réponses.

Voyons ce que votre code fait réellement:

C:

  • imprimer une seule chaîne, "Hello world\n"

C++:

  • diffuser la chaîne "Hello world" dans std::cout
  • transférer le manipulateur std::endl dans std::cout

Apparemment, votre code C++ fait deux fois plus de travail. Pour une comparaison équitable, nous devrions combiner ceci:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

… Et tout à coup votre code d'assemblage pour main ressemble beaucoup à C:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

En fait, on peut comparer les codes C et C++ ligne par ligne, et il y a très peu de différences:

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

La seule différence réelle est qu'en C++, nous appelons operator << avec deux arguments (std::cout et la chaîne). Nous pourrions même supprimer cette légère différence en utilisant un équivalent plus proche: fprintf, qui possède également un premier argument spécifiant le flux.

Cela laisse le code d'assemblage pour _GLOBAL__sub_I_main, qui est généré pour C++ mais pas C. C'est la seule véritable surcharge visible dans cette liste Assembly (il y a plus, une surcharge invisible pour les deux langues, bien sûr). Ce code effectue une configuration unique de certaines fonctions de bibliothèque standard C++ au début du programme C++.

Mais, comme expliqué dans d’autres réponses, la différence pertinente entre ces deux programmes ne se trouve pas dans la sortie Assembly de la fonction main, étant donné que tous les efforts lourds s’effectuent dans les coulisses.

133
Konrad Rudolph

Il est célèbre qu'en C++, vous payez pour ce que vous mangez. Donc, dans ce cas, que vais-je payer?

C'est simple. Vous payez pour std::cout. "Vous ne payez que ce que vous mangez" ne signifie pas "vous obtenez toujours les meilleurs prix". Bien sûr, printf est moins cher. On peut soutenir que std::cout est plus sûr et plus polyvalent, son coût plus élevé est donc justifié (il coûte plus cher, mais offre plus de valeur), mais cela passe à côté de l'essentiel. Vous n'utilisez pas printf, vous utilisez std::cout, vous payez donc pour utiliser std::cout. Vous ne payez pas pour utiliser printf.

Un bon exemple est celui des fonctions virtuelles. Les fonctions virtuelles ont des besoins en termes de coût d'exécution et d'espace, mais uniquement si vous les utilisez . Si vous n'utilisez pas de fonctions virtuelles, vous ne payez rien.

Quelques remarques

  1. Même si le code C++ évalue davantage d'instructions d'assembly, il n'en reste qu'une poignée, et toute surcharge de performances est toujours susceptible d'être dépassée par les opérations d'E/S réelles.

  2. En fait, parfois, c'est même mieux que "en C++, vous payez pour ce que vous mangez". Par exemple, le compilateur peut déduire que l'appel de fonction virtuelle n'est pas nécessaire dans certaines circonstances et le transformer en appel non virtuel. Cela signifie que vous pouvez obtenir des fonctions virtuelles pour gratuitement . N'est-ce pas génial?

53
el.pescado

La "Liste de montage pour printf" n'est PAS destinée à printf, mais à des options de vente (type d'optimisation du compilateur?); printf est beaucoup plus complexe que met ... n'oubliez pas!

47

Je vois des réponses valables ici, mais je vais entrer un peu plus dans les détails.

Allez au résumé ci-dessous pour trouver la réponse à votre question principale si vous ne voulez pas parcourir tout ce mur de texte.


Abstraction

Donc, dans ce cas, que vais-je payer?

Vous payez pour l'abstraction . Etre capable d'écrire un code plus simple et plus convivial a un coût. En C++, qui est un langage orienté objet, presque tout est un objet. Lorsque vous utilisez un objet, trois choses principales se passent toujours sous le capot:

  1. Création d'objet, essentiellement allocation de mémoire pour l'objet lui-même et ses données.
  2. Initialisation d'objet (généralement via une méthode init()). En général, l’allocation de mémoire se passe sous le capot comme première étape de cette étape.
  3. Destruction d'objet (pas toujours).

Vous ne le voyez pas dans le code, mais chaque fois que vous utilisez un objet, les trois opérations ci-dessus doivent se produire. Si vous deviez tout faire manuellement, le code serait évidemment beaucoup plus long.

Désormais, l’abstraction peut être réalisée efficacement sans ajouter de temps système: les compilateurs et les programmeurs peuvent utiliser des méthodes d’introduction et d'autres techniques pour supprimer les frais généraux d’abstraction, mais ce n’est pas votre cas.

Qu'est-ce qui se passe réellement en C++?

La voici décomposée:

  1. La classe std::ios_base est initialisée, c'est-à-dire la classe de base pour tout ce qui est lié aux E/S.
  2. L'objet std::cout est initialisé.
  3. Votre chaîne est chargée et transmise à std::__ostream_insert, qui (comme vous l'avez déjà compris par son nom) est une méthode de std::cout (essentiellement l'opérateur <<) qui ajoute une chaîne au flux. .
  4. cout::endl est également transmis à std::__ostream_insert.
  5. __std_dso_handle est transmis à __cxa_atexit, une fonction globale chargée de "nettoyer" avant de quitter le programme. __std_dso_handle lui-même est appelé par cette fonction pour désallouer et détruire les objets globaux restants.

Donc, en utilisant C == ne payant rien?

Dans le code C, très peu d’étapes se passent:

  1. Votre chaîne est chargée et passée à puts via le registre edi.
  2. puts est appelé.

Pas d'objets n'importe où, donc pas besoin d'initialiser/détruire quoi que ce soit.

Cela ne signifie cependant pas que vous ne "payez" rien en C . Vous payez toujours pour l'abstraction, ainsi que pour l'initialisation de la bibliothèque standard C et la résolution dynamique de la fonction printf (ou, en fait, puts, optimisée par le compilateur puisque vous n'avez besoin d'aucune chaîne de formatage. ) arrive encore sous le capot.

Si vous deviez écrire ce programme en pur assemblage, cela ressemblerait à ceci:

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

Ce qui, fondamentalement, ne fait qu'appeler la writesyscall suivie de la exit syscall. Maintenant ceci serait le strict minimum pour accomplir la même chose.


Résumer

C est beaucoup plus nu-osseux , et ne fait que le strict minimum nécessaire, laissant le contrôle total à l'utilisateur, qui est en mesure d'optimiser et de personnaliser fondamentalement tout ce qu'ils veulent. Vous indiquez au processeur de charger une chaîne dans un registre, puis appelez une fonction de bibliothèque pour utiliser cette chaîne. C++ est en revanche beaucoup plus complexe et abstrait . Cela présente un avantage énorme lors de l'écriture de code compliqué, et permet une écriture plus facile et un code plus convivial, mais cela a évidemment un coût. Les performances en C++ présenteront toujours un inconvénient si on les compare à C, car , le C++ offre plus que ce qui est nécessaire pour accomplir de telles tâches de base, ce qui alourdit davantage la charge .

Répondant à votre question principale :

Est-ce que je paie pour ce que je ne mange pas?

Dans ce cas spécifique, yes . Vous ne tirez pas parti de tout ce que le C++ peut offrir de plus que le C, mais c'est simplement parce qu'il n'y a rien dans ce morceau de code aussi simple que le C++ qui puisse vous aider: il est si simple que vous n'avez vraiment pas besoin de C++.


Oh, et encore une chose!

Les avantages du C++ peuvent ne pas sembler évidents au premier abord, puisque vous avez écrit un programme très simple et petit, mais regardez un exemple un peu plus complexe et voyez la différence (les deux programmes font exactement la même chose):

C:

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C++ :

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

J'espère que vous pouvez voir clairement ce que je veux dire ici. Vous remarquerez également qu’en C, vous devez gérer la mémoire à un niveau inférieur en utilisant malloc et free, comment vous devez faire plus attention à l’indexation et aux tailles et à la plus grande précision lors de la saisie et de la saisie. impression.

44
Marco Bonelli

Il y a quelques idées fausses pour commencer. Tout d’abord, le programme C++ ne donne pas 22 instructions, c’est plus comme 22 000 instructions (j’ai tiré ce numéro de mon chapeau, mais c’est à peu près dans la stade de baseball). De plus, le code C ne donne pas 9 instructions non plus. Ce ne sont que ceux que vous voyez.

Après avoir fait beaucoup de choses que vous ne voyez pas, le code C appelle une fonction du CRT (qui est généralement présente, mais pas nécessairement, sous la forme d'une bibliothèque partagée), puis ne ne vérifie pas la valeur de retour ou ne gère pas les erreurs, et échoue. Selon les paramètres du compilateur et de l'optimisation, il n'appelle même pas vraiment printf mais puts, ou encore quelque chose de plus primitif.
Vous auriez pu écrire plus ou moins le même programme (à l’exception de certaines fonctions init invisible) en C++ également, si seulement vous appeliez la même fonction de la même manière. Ou, si vous voulez être super-correct, cette même fonction préfixée par std::.

Le code C++ correspondant n’est en réalité pas du tout la même chose. Bien que tout <iostream> soit bien connu pour être un gros cochon laid qui ajoute une surcharge énorme aux petits programmes (dans un "vrai" programme, vous ne remarquez pas tellement cela), une interprétation un peu plus juste est: qu'il fait énormément de choses que vous ne voyez pas et qui ne fonctionnent que . Y compris, sans toutefois s'y limiter, le formatage magique de presque toutes les choses au hasard, y compris les formats de nombres et les paramètres régionaux différents, etc., la mise en mémoire tampon et le traitement correct des erreurs. La gestion des erreurs? Eh bien oui, devinez quoi, la sortie d'une chaîne peut en réalité échouer, et contrairement au programme C, le programme C++ ne l'ignorerait pas en silence. Considérant ce que std::ostream fait sous le capot, et sans que personne ne le sache, il est en fait assez léger. Pas comme si je l'utilisais parce que je déteste la syntaxe du flux avec passion. Mais quand même, c'est assez génial si on considère ce que ça fait.

Mais bien sûr, C++ dans son ensemble est pas aussi efficace que C peut l'être. Cela ne peut pas être aussi efficace car ce n'est pas la même chose et ce n'est pas faire la même chose. Si rien d'autre, C++ génère des exceptions (et du code pour les générer, les gérer ou y échouer) et donne certaines garanties que C ne donne pas. Donc, bien sûr, un programme C++ doit nécessairement être un peu plus grand. Dans l’ensemble, cela n’a aucune importance. Au contraire, pour les programmes réels , j'ai rarement constaté que C++ était plus performant car, pour une raison ou une autre, il semble prêter à des optimisations plus favorables. . Ne me demandez pas pourquoi en particulier, je ne saurais pas.

Si, au lieu de "feu-et-oublier-l'espoir-pour-le-meilleur", vous vous souciez d'écrire du code C qui est correct (c'est-à-dire que vous vérifiez réellement erreurs, et le programme se comporte correctement en présence d’erreurs), la différence est alors marginale, si existante.

27
Damon

Vous payez pour une erreur. Dans les années 80, lorsque les compilateurs n'étaient pas assez efficaces pour vérifier les chaînes de format, la surcharge de l'opérateur était considérée comme un bon moyen de renforcer l'apparence de la sécurité de type pendant io. Cependant, chacune de ses bannières est mal implémentée ou en faillite conceptuelle:

<iomanip>

La partie la plus répugnante du flux C++ io api est l’existence de cette bibliothèque d’entêtes de mise en forme. En plus d'être dynamique et laide et sujet aux erreurs, il associe le formatage au flux.

Supposons que vous souhaitiez imprimer une ligne avec un caractère hexadécimal rempli de zéros à 8 chiffres, un entier non signé, suivi d'un espace suivi d'un double avec 3 décimales. Avec <cstdio>, vous obtenez une chaîne de format concise. Avec <ostream>, vous devez enregistrer l’ancien état, définir l’alignement sur droite, définir le caractère de remplissage, définir la largeur de remplissage, définir la base sur hexadécimal, afficher l’entier, restaurer l’état enregistré (sinon votre formatage entier polluera votre formatage flottant ), affiche l’espace, définit la notation sur fixed, définit la précision, affiche le double et la nouvelle ligne, puis rétablit l’ancienne mise en forme.

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

Surcharge de l'opérateur

<iostream> est l'affiche de l'utilisation de la surcharge d'opérateur:

std::cout << 2 << 3 && 0 << 5;

Performance

std::cout est plusieurs fois plus lent printf(). La fonctionnalité et la dépêche virtuelle qui sévit ont leurs conséquences.

Sécurité du fil

<cstdio> et <iostream> sont tous les deux thread-safe, dans la mesure où chaque appel de fonction est atomique. Mais, printf() fait beaucoup plus par appel. Si vous exécutez le programme suivant avec l'option <cstdio>, vous ne verrez qu'une ligne de f. Si vous utilisez <iostream> sur un ordinateur multicœur, vous verrez probablement autre chose.

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

La réponse à cet exemple est que la plupart des gens exercent la discipline de ne jamais écrire dans un seul descripteur de fichier à partir de plusieurs threads. Eh bien, dans ce cas, vous devrez observer que <iostream> prendra utilement un verrou sur chaque << et sur chaque >>. Alors que dans <cstdio>, vous ne verrouillez pas aussi souvent et vous avez même la possibilité de ne pas le verrouiller.

<iostream> utilise plus de verrous pour obtenir un résultat moins cohérent.

22
KevinZ

En plus de ce que toutes les autres réponses ont dit,
Il y a aussi le fait que std::endl est pas identique à '\n'.

C'est une idée fausse malheureusement commune. std::endl ne signifie pas "nouvelle ligne",
cela signifie "imprimer une nouvelle ligne et ensuite vider le flux". Flushing n'est pas bon marché!

Ignorant complètement les différences entre printf et std::cout pendant un moment, pour être fonctionnellement équivalent à votre exemple C, votre exemple C++ devrait ressembler à ceci:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

Et voici un exemple de ce que vos exemples devraient être si vous incluez le rinçage.

C

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

Lorsque vous comparez du code, , vous devez toujours faire attention à ce que vous compariez de la même manière et que vous compreniez les implications de ce que fait votre code. Parfois, même les exemples les plus simples sont plus compliqués que certains ne le réalisent.

18
Pharap

Bien que les réponses techniques existantes soient correctes, je pense que la question découle finalement de cette idée fausse:

Il est célèbre qu'en C++, vous payez pour ce que vous mangez.

Ceci est juste un discours marketing de la communauté C++. (Pour être juste, il y a un discours marketing dans toutes les communautés linguistiques.) Cela ne veut pas dire quelque chose de concret sur lequel vous pouvez vraiment compter.

"Vous payez pour ce que vous utilisez" est censé signifier qu'une fonctionnalité C++ n'a de frais généraux que si vous utilisez cette fonctionnalité. Mais la définition de "fonctionnalité" n’est pas infiniment granulaire. Vous finirez souvent par activer des fonctionnalités ayant plusieurs aspects, et même si vous n’avez besoin que d’un sous-ensemble de ces aspects, cela n’est souvent ni pratique ni possible. pour la mise en œuvre d'apporter la fonctionnalité en partie.

En général, beaucoup de langues (même si ce n’est pas toutes les langues) s’efforcent d’être efficaces, avec plus ou moins de succès. C++ est quelque part sur la balance, mais sa conception n’a rien de spécial ou de magique qui lui permettrait de réussir parfaitement dans cet objectif.

16

Les fonctions d'entrée/sortie en C++ sont écrites avec élégance et sont conçues pour être simples à utiliser. À bien des égards, ils constituent une vitrine pour les fonctionnalités orientées objet de C++.

En contrepartie, vous perdez un peu de performances, mais cela est négligeable comparé au temps mis par votre système d'exploitation à gérer les fonctions à un niveau inférieur.

Vous pouvez toujours utiliser les fonctions de style C, car elles font partie de la norme C++, ou peut-être même abandonner la portabilité et utiliser des appels directs vers votre système d'exploitation.

12
Bathsheba

Comme vous l'avez vu dans d'autres réponses, vous payez lorsque vous liez des bibliothèques générales et appelez des constructeurs complexes. Il n'y a pas de question particulière ici, plus un reproche. Je soulignerai certains aspects du monde réel:

  1. Barne avait pour principe de conception de ne jamais laisser l'efficacité être une raison de rester en C plutôt qu'en C++. Cela dit, il faut être prudent pour obtenir ces gains d'efficacité, et des gains d'efficacité occasionnels ont toujours fonctionné, mais n'étaient pas "techniquement" au sens de la spécification C. Par exemple, la disposition des champs de bits n'était pas vraiment spécifiée.

  2. Essayez de regarder à travers ostream. Oh mon dieu, c'est gonflé! Je ne serais pas surpris de trouver un simulateur de vol là-bas. Même la fonction printf () de stdlib utilise habituellement environ 50K. Ce ne sont pas des programmeurs paresseux: la moitié de la taille de printf est liée à des arguments de précision indirecte que la plupart des gens n'utilisent jamais. Presque toutes les bibliothèques de processeurs vraiment contraintes créent leur propre code de sortie au lieu de printf.

  3. L'augmentation de la taille offre généralement une expérience plus contenue et plus flexible. Par analogie, un distributeur automatique vendra une tasse de substance ressemblant à du café pour quelques pièces et la transaction entière prend moins d'une minute. Entrer dans un bon restaurant implique une table, être assis, commander, attendre, prendre une bonne tasse, recevoir une facture, payer avec le choix de formes, ajouter un pourboire et être souhaité une bonne journée en partant. C'est une expérience différente et plus pratique si vous passez chez des amis pour un repas complexe.

  4. Les gens écrivent encore ANSI C, mais rarement K & R C. Mon expérience est que nous le compilons toujours avec un compilateur C++ en utilisant quelques ajustements de configuration pour limiter ce qui est entraîné. Il existe de bons arguments pour d’autres langages: Go supprime la surcharge polymorphe et le préprocesseur fou ; il y a eu de bons arguments en faveur d'un emballage sur le terrain et d'une disposition de la mémoire plus intelligents. IMHO Je pense que toute conception de langage devrait commencer par une liste d'objectifs, un peu comme le Zen of Python .

Ce fut une discussion amusante. Vous vous demandez pourquoi vous ne pouvez pas avoir des bibliothèques magiquement petites, simples, élégantes, complètes et flexibles?

Il n'y a pas de reponse. Il n'y aura pas de réponse. C'est la réponse.

1
Charles Merriam