Le standard C++0x a enfin été voté

Posté par (page perso) . Modéré par Lucas Bonnet. Licence CC by-sa
76
18
août
2011
C et C++

Le nouveau standard du langage C++ a été voté ce vendredi 12 août. Initialement prévu pour 2008 ou 2009, il a pris du retard, notamment à cause de problèmes avec certaines fonctionnalités. Malgré cette date, beaucoup l'appellent quand même C++0x et il suffit de le nommer C++0xB pour que le nom soit conservé. La publication officielle ne devrait cependant pas arriver avant quelques mois.

Heureusement pour les utilisateurs, ces fonctionnalités ont déjà commencé à être implémentées dans les différents compilateurs et bibliothèques standard. Ceci devrait permettre de ne pas attendre trop longtemps avant de pouvoir profiter de ces nouveautés, voire d'en profiter dès à présent pour certaines. Par exemple, sous GCC, il faut passer l'option -std=c++0x au compilateur pour utiliser des évolutions qui sont déjà implémentées.

Une sélection des principales nouveautés est présentée en seconde partie de dépêche.

Merci à moules< pour son aide lors de la rédaction de cette dépêche.

Sommaire

Les évolutions du langage

La délégation de constructeur

Il est désormais possible d'appeler un constructeur dans un autre (du même objet), cela permet d'écrire plus facilement des contructeurs par défaut. Par exemple, le code suivant :

class MyClass {
    private:
        constructor(int num);

    public:
        MyClass(int num) {constructor(num);};
        MyClass() {constructor(42);};
}

pourra être remplacé par :

class MyClass {
    public:
        MyClass(int num);
        MyClass() {MyClass(42)};
}

L'inférence de type

L'inférence de types est un mécanisme qui permet à un compilateur ou un interpréteur de rechercher automatiquement les types associés à des expressions, sans qu'ils soient indiqués explicitement dans le code source. Elle est disponible via deux moyen. D'abord avec le mot-clef auto quand on déclare une variable, cette dernière prendra le type de son assignation. Cela donne :

auto maVariable = 5;

Et la variable aura le type int. Bien sûr, ce n'est pas à utiliser dans n'importe quel cas et l'utilisation la plus courante sera sans doute lors du parcours d'une liste dans une boucle :

for (std::vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)

sera remplacé par :

for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

Une autre utilisation de l'inférence de type se fait avec le mot-clef en:decltype. Cela permet de typer une variable non pas avec le type de l'assignation mais avec celui d'une autre variable :

char a;
decltype(a) b=5;

Dans ce cas, b sera du type char. L'utilisation devient intéressante lorsqu'on utilise une variable avec auto ou lors de l'utilisation importante de la surcharge d'opérateur et des types spécialisés.

Le foreach

Il est désormais possible de parcourir une liste d'élément assez simplement :

int my_array[5] = {1, 2, 3, 4, 5};
for (int &x : my_array) {
    x *= 2;
}

Cette forme est utilisable avec les tableaux de style C et tous les objets qui possèdent les méthodes begin() et end() renvoyant un itérateur.

Les fonctions lambdas

Les fonctions lambdas sont des fonctions anonymes qui peuvent être définies au mileu d'une autre fonction. Elle sont utiles pour définir une action sans devoir renvoyer le lecteur et l'auteur du code à un autre endroit. Par exemple, avec la fonction std::find, il peut être utile de définir directement le prédicat plutôt que d'indiquer le nom d'une fonction dont le contenu est inconnu.

Les fonctions lambdas s'utilisent de la manière suivante :

[](int x, int y) { return x + y; }

Le type du retour peut être explicitement défini :

[](int x, int y) -> int { int z = x + y; return z; }

Les listes d'initialisations

Les listes d'initialisations permettent d'initialiser les objets en passant une liste de variable entre accolades, comme pour les tableaux et les structures. Pour que cela fonctionne, il faut définir un constructeur qui prend un seul argument de type std::initializer_list. Concrètement cela donne :

class SequenceClass {
public:
    SequenceClass(std::initializer_list<int> list);
};

Et cela s'utilise :

SequenceClass some_var = {1, 4, 5, 6};

Et cela peut même s'utiliser avec une fonction.

Le pointeur NULL

Pour comprendre son intérêt, il faut savoir que la constante NULL est, la plupart du temps, définie comme 0, qui est de type int, ce qui peut poser des problèmes lors de l'appel de méthodes surchargées. Il a donc été décidé d'introduire nullptr qui peut avoir comme type n'importe quelle valeur de pointeur ou un booléen mais pas un autre type :

char *pc = nullptr;     // OK
int  *pi = nullptr;     // OK
bool   b = nullptr;     // OK. b est faux.
int    i = nullptr;     // KO

Ceci permet lorsqu'on a deux fonctions telles que :

void foo(char *);
void foo(int);

que lorsque l'appel suivant est écrit :

foo(nullptr);

le compilateur choisisse la fonction foo(char *) au lieu de foo(int), ce qui est le comportement le plus souvent désiré.

Les chaînes de caractères

Afin d'améliorer la prise en charge de l'unicode, des nouveaux préfixes ont été introduits :

u8"Une chaîne UTF-8"
u"Une chaîne UTF-16"
U"Une chaîne UTF-32"

Pour cela, les types char16_t et char32_t ont été créés. Il est aussi possible d'insérer un caractère unicode en tapant son code en hexadécimal et le préfixant de \u, par exemple \u2603.

Les chaînes raw ont aussi été ajoutées, elle permettent d'éviter de devoir échapper les caractères :

R"(Un slash \ au milieu)"
R"delimiteur(Un slash \ et des guillemets " au milieu )delimiteur"

(Attention, la coloration syntaxique n'est pas encore prévue pour ce nouveau standard).
Comme on le voit, il est possible de définir son propre délimiteur pour ces chaînes afin de pouvoir mettre n'importe quel texte.

Les évolutions de la bibliothèque standard

Les threads

Les threads sont pris officiellement en charge par la bibliothèque standard via l'objet std::thread. Afin de pouvoir les utiliser efficacement, les mutex, variables conditionnelles, les tâches futures et les opérations atomiques sont aussi disponibles.

On peut quand même regretter l'absence de sémaphore dans la spécification.

Containers

Les tuples

Le tuple, ou n-uplets, est une collection ordonnée de n objets. Cette structure est désormais dipsonible et s'utilise de la manière suivante :

typedef std::tuple <int, double, long &, const char *> test_tuple;
long lengthy = 12;
test_tuple proof (18, 6.5, lengthy, "Ciao!");
 
lengthy = std::get<0>(proof);  // Assign to 'lengthy' the value 18.
std::get<3>(proof) = " Beautiful!";  // Modify the tuple’s fourth element.

Les tables de hachage

Quatre nouveaux containers ont été ajoutés :

  • std::unordered_set
  • std::unordered_multiset
  • std::unordered_map
  • std::unordered_multimap

Les versions multi acceptent des entrées avec des clefs identiques. Les set sont des ensembles d'objets non-ordonnés tandis que les map font correspondre à chaque entrée une clef.

Les expressions régulières

Les expressions régulières font leur entrée dans la bibliothèques standard. La fonction std::regex_search est utilisée pour chercher une correspondance tandis que la fonction std::regex_replace sert à remplacer des occurrences dans la chaîne.

const char *reg_esp = R"([ ,.\t\n;:])"; // Une liste de caractères de séparation
    // on utilise les chaîne raw pour éviter de doubler les antislash
std::regex rgx(reg_esp);  
std::cmatch match;  // match est utilisé pour stocker le résultat 
const wchar32_t *target = u"Université de l'invisible - Ankh-Morpork";
 
// Identifie tous les mots de 'target' séparés par les caractères de 'reg_esp'.
if( std::regex_search( target, match, rgx ) ) { // Si des mots séparés par les caractères sont présent.
    const size_t n = match.size();
    for( size_t a = 0; a < n; a++ ) {
        std::string str( match[a].first, match[a].second );
        std::cout << str << std::endl;
    }
}

Gestion des nombres pseudo-aléatoires

Cette nouvelle version du langage introduit une gestion supplémentaire des nombres pseudo-aléatoires, en remplacement de la vénérable fonction rand() que C++ avait conservé du C.

Un objet de génération de nombre pseudo-aléatoire est composé de deux mécanismes :

  • un générateur qui produit les nombres pseudo-aléatoires ;
  • un distributeur, qui détermine la série et la distribution mathématique du résultat.

Contrairement à la fonction standard C rand, le mécanisme du C++1x fournit trois algorithmes de génération :

  • linear_congruential, ayant une qualité de génération et une rapidité moyenne, mais est très économe en mémoire ;
  • subtract_with_carry, rapide mais de qualité moyenne ;
  • mersenne_twister, de bonne qualité et très rapide, mais a l'inconvénient d'être plus gourmand en mémoire.

En revanche, un nombre appréciable de distributeurs existe : uniform_int_distribution, bernoulli_distribution (à ne pas confondre avec berlusconi_distribution), geometric_distribution, poisson_distribution, binomial_distribution, uniform_real_distribution, exponential_distribution, normal_distribution, et gamma_distribution.

Un exemple d'utilisation :

#include <random>
#include <functional>
 
std::uniform_int_distribution<int> distribution(0, 99);
std::mt19937 engine; // Mersenne twister MT19937
auto generator = std::bind(distribution, engine);
int random = generator();  // Génère un entier entre 0 et 99.
int random2 = distribution(engine); // Génère un autre entier en utilisant directement le générateur avec le moteur de distribution.

L'utilisation de ces objets permet de ne pas laisser à l'application le soin de s'occuper de l'initialisation de la graine, ou de mal l'initialiser, ce qui est source de nombreuses failles de sécurité.

Cet article est, en partie, une traduction de l'article Wikipedia C++0x

  • # à l'assaut de Microsoft maintenant !

    Posté par . Évalué à -10.

    et preums !

    • [^] # Re: à l'assaut de Microsoft maintenant !

      Posté par . Évalué à 7.

      Je ne vois pas le rapport (à part le temps que mettra Visual C++11 à respecter entièrement la norme, mais bon…)

      • [^] # Re: à l'assaut de Microsoft maintenant !

        Posté par (page perso) . Évalué à 8.

        Visual Studio 2010 implémente déjà un certain nombre de features de C++0x (auto, lambda, nullptr, rvalue). Ils ont, depuis l'horrible VC6, embauché quelques mecs qui comprennent le C++ (entre autre Herb Sutter) et je pense qu'ils respecteront relativement rapidement le standard. (Pour les parties librairies, y'a qu'a faire un copier coller depuis boost pour la majorité :D).

        • [^] # Re: à l'assaut de Microsoft maintenant !

          Posté par (page perso) . Évalué à 2.

          Ou alors ils vont pousser le C# en abandonnant progressivement le C++ (ou tout du moins en l'handicapant).

          « En fait, le monde du libre, c’est souvent un peu comme le parti socialiste en France » Troll

  • # C

    Posté par (page perso) . Évalué à 10.

    Dommage que ce soit une nouvelle norme C++ et non une norme C/C++, parce que certaines de ces améliorations pourraient tout à fait s'appliquer au C. Typiquement, le foreach.

    • [^] # Re: C

      Posté par . Évalué à 10.

      • [^] # Re: C

        Posté par (page perso) . Évalué à 2.

        OMGAD

        Optional features in C1X
        [...]
        Variable length arrays STDC_NO_VLA

        Le meilleur du best of des features du C99 deviendrait optionnel.

        • [^] # Re: C

          Posté par . Évalué à -2.

          C'est clair !

        • [^] # Re: C

          Posté par . Évalué à 1.

          Ben de tout de manière il n'était pas supporté par tout.

  • # templates variadiques

    Posté par (page perso) . Évalué à 7.

    Dans la liste des nouveautés, il y en a également une très intéressante: les «templates variadiques». Cela permet d'avoir des templates avec un nombre variable d'arguments.

    Jusqu'à présent, je m'en sortais avec des macros ou avec les va_list du C, mais il parait que ce n'est pas bien.

    • [^] # Re: templates variadiques

      Posté par . Évalué à 10.

      Si tu veux parler de nouvelles fonctionnalités, alors parlons en de mes préférées :

      • Pointeurs intelligents (ceux de Boost).

        {
            std::scoped_ptr<monobjet> e(new monobjet);
            
            if (pas_content())
                return;
        
            faire_des_autres_trucs(e);
            if (vraiment_pas_content())
                throw PasContentException();
        
            return;
        }
        

        e sera automatiquement détruit si on sort de la paire d'accolade. Il n'y a plus aucune raison d'utiliser delete, et on va éliminer quasiment tout les leaks avec ça. Existe aussi en version avec transfert de propriété (unique_ptr, pour remplacer auto_ptr) et en version avec un compteur de référence (shared_ptr)
        Et après vous comprendrez pourquoi les C++eux vous disent qu'ils n'ont pas besoin de GC.
      • Initialisation de membres dans une classe

        class A{
            int i = 42;
        };
        

        i prendra comme valeur 42 par défaut, sauf si redéfini par un constructeur.
      • Utilisation de classes locales comme classe template :

        void oh_une_func(std::vector<int>& i) {
            class compare_last_decimal {
                operator () (int a, int b) {
                    return (a%10) < (a%10);
                }
            };
            std::sort(i.begin(), i.end(), compare_last_decimal());
        }
        
      • Move semantics :

        std::vector<int> ma_fonction();
        ...
        std::vector<int> mon_resultat = ma_fonction();
        

        Avant, ce code pouvait impliquer une copie du vecteur. Maintenant, ça n'est qu'une affectation de pointeur. En gros, il y a un constructeur A::A(A&& old) et un opérateur d'affectation A& A::operator=(A&& old) qui peut être utilisé quand on sait que "old" va être prochainement détruit.
      • Suppressions et définitions par défaut :

        class A{
            A() = default;
            A(int i);
            A(const A& ) = delete;
            A(A&&) = delete;
        };
        

        Le constructeur par défaut sera synthétisé automatiquement (ça fera une erreur si ça échoue, pas comme avant) et sans avoir à le faire à la main si on en défini un autre.
        La construction par copie est supprimée (elle est automatique sinon) tout comme la construction par déplacement.
      • [^] # Re: templates variadiques

        Posté par (page perso) . Évalué à 2.

        Ma grande question est de savoir si un truc comme ça est possible, avec le polymorphisme de fonction:

        class A { ... }
        class B: public A { ... }
        class C: public A { ... }
        
        bool fctB(B x) { ... }
        bool fctC(C x) { ... }
        
        std::vector<std::function<bool (A)>> list = {&fctB, &fctC};
        
        B objB;
        C objC;
        list[0](objB);
        list[1](objC);
        
        

        Ca me serait extrêmement utile pour gérer des piles d’évènement sans devoir faire un cast chiant :)

        • [^] # Re: templates variadiques

          Posté par . Évalué à -2.

          Je vais me faire grassement moinsser, désolé aux familles toussa.

          Mon opinion : à mort les templates et la librarie "Standard".

          Vive Qt, vive Java, vive le C, à la limite vive le C++ sans template et sans RTTI. Mais je DÉTESTE cette putain de STL. À part pouvoir procurer de la fierté en permettant d'écrire des choses compliquées et coûteuses pour faire de l'ultra trivial ... je vois pas. Ok, ça fait plus que les macros.

          Je ne parle même pas de la difficulté à (re)faire du C++ "académique" après avoir fait du Groovy ou du Python.

          D'après vous quelle est le pire des bouquins pour apprendre le C++ (on peut généraliser à "pour apprendre un langage")? le STROUSTRUP? il y a pire?

          • [^] # Re: templates variadiques

            Posté par (page perso) . Évalué à 10.

            Ah c'est marrant moi je dirais vive le c++ avec son rtti et ses templates (utilisés a doses raisonnables) , mais non aux exceptions qui puent et aux iostreams qui sont la honte des flux d'entrée sortie

      • [^] # Re: templates variadiques

        Posté par (page perso) . Évalué à 2.

        Les move semantics sont un sujet un petit peu complexe, mais ce qui est agréable, c'est que l'on peut s'y mettre doucement. Sans rien faire, déjà, le code utilisant lourdement la bibliothèque standard profitera d'une amélioration de performances. Ensuite, l'on peut commencer à écrire des constructeurs par copie pour ses objets les plus utilisés, et continuer dans cette voie.

        J'aime également beaucoup std::chrono, qui permet de mesurer le temps et de faire des calculs sur des intervalles. Les templates sont intelligemment utilisées pour vérifier les unités à la compilation, afin de ne pas confondre ses secondes avec ses microsecondes.

      • [^] # Re: templates variadiques

        Posté par (page perso) . Évalué à 4.

        Et quel est le surcout des smart pointeurs au final, faut-il faire attention a ne pas trop en abuser ? Parce qu'a priori a chaque creation ou copie, il y a incrementation atomique d'un compteur de references, il me semble que ce genre d'operation prend typiquement 100 cycles sur un cpu moderne.

        • [^] # Re: templates variadiques

          Posté par . Évalué à 3.

          Là tu parle des shared_ptr, je pense que dans la plupart des cas c'est les autres types qui devraient être utilisés.

          Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

          • [^] # Re: templates variadiques

            Posté par (page perso) . Évalué à 2.

            ah oui pardon effectivement je parlais des shared_ptr et non des scoped_ptr dont j'ignorais d'ailleurs l'existence

            • [^] # Re: templates variadiques

              Posté par . Évalué à 2.

              Même pour shared_ptr, à moins de refiler ton pointeur comme une STD à tous les objets qui passent, tu ne paies le coût de l'opération atomique qu'occasionnellement.

              • [^] # Re: templates variadiques

                Posté par . Évalué à 2.

                Si on utilise des objets avec des shared_ptr mais que l'on sait que ces objets ne seront jamais partagé entre 2 threads différents : est-ce que l'on paye aussi le coût de l'opération atomatique ?

                Est-ce qu'il existe un shared_nothreading_sharing_ptr< T > ?

                • [^] # Re: templates variadiques

                  Posté par (page perso) . Évalué à -1.

                  Est-ce que le standard garanti que std::shared_ptr est thread-proof ?
                  Est-ce que si std::shared_ptr est conçu pour fonctionner dans un contexte multi-thread, le recours à un mutex est systématique même quand aucun mécanisme de multi-threading n'est activé ?

                  Il faudrait se plonger dans le draft. Je t'en prie, passe devant.

                • [^] # Re: templates variadiques

                  Posté par . Évalué à 4.

                  D'après la doc de boost,

                  shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.)

                  Donc si tu as deux threads, l'un qui lit le pointeur et l'autre qui l'écrit, le résultat est indéfini (race condition). À toi de fournir les verrous nécessaires pour que cela n'arrive pas.

                  La norme n'impose pas d'être lock-free.

                  Cependant, toujours d'après la même page,

                  Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on the following platforms:

                  • GNU GCC on x86 or x86-64;
                    • GNU GCC on IA64;
                    • Metrowerks CodeWarrior on PowerPC;
                    • GNU GCC on PowerPC;
                    • Windows.

                  If your program is single-threaded and does not link to any libraries that might have used shared_ptr in its default configuration, you can #define the macro BOOST_SP_DISABLE_THREADS on a project-wide basis to switch to ordinary non-atomic reference count updates.

                  Donc il existe un moyen d'être partagé sans passer par des instructions atomiques.

                  • [^] # Re: templates variadiques

                    Posté par . Évalué à 3.

                  • [^] # Re: templates variadiques

                    Posté par . Évalué à 1.

                    Il faut faire attention qu'il y a 2 attributs manipulés par un shared_ptr :

                    1. Le pointeur vers l'objet
                    2. Le compteur de référence

                    La garantie identique au type pointeur de base que tu évoque dans le premier paragraphe concerne l'accès au pointeur (par exemple les fonctions get(), reset(), operator*())

                    Le point que tu évoque sur l'aspect lock-free concerne le compteur de référence. C'est à dire que plusieurs threads peuvent copier le shared_ptr, puis détruire des shared_ptrs, l'accès au compteur de référence est protégé par l'utilisation de fonctions atomiques (lorsque le CPU et boost les gèrent).

                    On peut regarder sur boost/smart_ptr/shared_ptr.hpp que la classe shared_ptr contient 2 membres :

                    • T * px
                    • boost::detail::shared_count pn

                    C'est cette deuxième classe qui utilise des fonctions atomiques définies dans sb_counted_base, on peut par exemple regarder boost/smart_ptr/detail/sp_counted_base_sync.hpp qui utilise les fonctions fournies par gcc (voir Atomic-Builtins).

                    Étienne

                  • [^] # Re: templates variadiques

                    Posté par . Évalué à 0.

                    J'ai bien vu BOOST_SP_DISABLE_THREADS mais c'est une macro qui change globalement le comportement des shared_ptr. C'est probablement un peu brutal puisque l'on ne peut plus mélanger les 2.

                    Après peut-etre aussi que les opérations atomiques ne sont pas si longues mais ca me fait toujours peur puisque ca signifie qu'il faut sortir du fond du processeur et aller jusque dans la mémoire ou les caches partagés.

                    • [^] # Re: templates variadiques

                      Posté par . Évalué à 4.

                      Honnêtement, à moins d'avoir des tonnes d'objets à la durée de vie extrêmement courte qui partagent le même pointeur (ce qui peut arriver hein), la plupart du temps les opérations atomiques sont noyées dans la masse des autres opérations qui de toute manière doivent accéder à la RAM. De plus, si tu n'utilises pas d'opération atomique, je suppose que tu n'utilises pas de verrous non plus (vu que leur garantie d'atomicité se base sur… les opérations atomiques!).

                      Enfin, j'ai envie de dire que si tu veux avoir du comptage de référence personnalisé, le plus simple est de passer par un autre type de pointeur intelligent : intrusive_ptr (qui embarque un compteur de réf et tu dois fournir l'implémentation de certaines méthodes pour décrire comment en profiter).

                      Enfin, d'expérience, savoir quand un pointeur partagé ne va jamais être utilisé par un thread est difficile à évaluer. Autant n'avoir que des pointeurs partagés thread-safe, ou aucun, ça simplifie grandement les choses.

        • [^] # Re: templates variadiques

          Posté par . Évalué à 2.

          Pas besoin de locker, ca utilise les operation d'incrémentation atomiques.
          Ca a un coût, certes, mais c'est quand même efficace.
          Ca vient de la lib boost, qui a servi de reference pour l'implémentation dans le standard.

          a++

          • [^] # Re: templates variadiques

            Posté par (page perso) . Évalué à 5.

            ouais mais le truc c'est qu'une operation atomique c'est pas loin de couter aussi cher que de locker un mutex. J'avais lu ça sur une page web qui donnait des mesures assez precises, mais je ne la retrouve pas. Il y a celle ci:

            http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

            qui indique que le mutex prend 0.2 microsec contre 0.05 microsec pour l'operation atomique, et ce dans le meilleur des cas, sur un core duo à 2GHz

            • [^] # Re: templates variadiques

              Posté par . Évalué à 5.

              Un mutex utilise au moins une instruction atomique justement pour garantir l'atomicité de la prise du mutex. Bref, un mutex va forcément coûter au moins aussi cher qu'une opération atomique, vu qu'elles sont à la base du verrouillage ... :-)

              Aussi, 0.2 µs / 0.05 µs = 4. Pour les applications qui ne font pas usage trop fréquent de variables partagées/sections critiques ce n'est sans doute pas bien méchant, mais si une structure de donnée est très utilisée/mise à jour par beaucoup d'acteurs (disons, si je suis dans un système multicœur/HTT avec 32 ou 64 threads), on peut vite se retrouver avec de gros soucis de contention. Et dans ce cas, le facteur 4 n'est plus négligeable.

              Enfin, étant donné que les structures de données non-blocantes sont extrêmement susceptibles aux bugs subtils, je me dis qu'il vaut sans doute mieux rester avec des structures à grain fin la plupart du temps ...

        • [^] # Re: templates variadiques

          Posté par (page perso) . Évalué à 3.

          J'avais fait un calcul que tu peux retrouver à la fin du tableau à l'adresse http://www.shadoware.org/post/2011/01/17/Performance-de-l-utilisation-de-QSharedPointer. La conclusion que j'en tire est pour une utilisation courante : pas de problème. Pour gérer des tableaux de milliard de pointeur, on peut se poser la question.

        • [^] # Re: templates variadiques

          Posté par . Évalué à 6.

          Sachant qu'un GC avec compteur de référence est vu comme le degré zéro du GC, cela te donne une idée de la performance au final.

          "La première sécurité est la liberté"

          • [^] # Re: templates variadiques

            Posté par (page perso) . Évalué à 6.

            Va falloir que tu revois tes standards. Le comptage de référence est loin d'être mauvais et est même parfais dans certains cas. C'est juste qu'il n'est adapté que à certains cas d'utilisation et pas au cas général.

            Il a deux inconvénients :
            - de base il ne gère pas les boucles de dépendances, mais il y a moyens de contourner ce problème, et dans certains cas on peut même garantir que cela ne ce produira pas ;
            - il y peu déclencher des libérations en chaine qui font qu'une simple libération peut au final prendre beaucoup de temps.

            Le deuxième inconvénient qui est généralement le plus sérieux, ne correspond au final qu'au cas standard ou la mémoire est libérée explicitement. De plus la grande majorité des algorithmes de GC fonctionne en "stop the world" et on donc exactement le même problème avec en plus le fait que la pause peut ce produire n'importe quand.

            Les quelques algos qui contournent ce problème sont très complexes et donc quand on peut s'en passer et les remplacer par un comptage de référence qui s'implémente très simplement, pourquoi ce privé ?

            Le comptage de référence, s'il est bien fait c'est-à-dire si le comptage est automatique et que les cycle sont gérés en cas de besoin, permet bien souvent d'avoir un GC beaucoup plus simple et tout aussi efficace, et surtout beaucoup moins buggé.

            • [^] # Re: templates variadiques

              Posté par . Évalué à 2.

              Oui enfin il y a quand même un problème avec le comptage de référence naïf (je dis "naïf" parce que je n'ai pas cherché s'il existait des variantes qui ne posent pas ce problème) : dans le cadre d'un programme multi-thread, tu es obligé d'utiliser des opérations atomiques pour incrémenter/décrémenter ton compteur. Et ça, en terme de performances, c'est la mort (instructions en elles-mêmes, sans parler du cache line bouncing).
              C'est l'une des principales raisons pour lesquelles CPython a toujours le GIL...

              • [^] # Re: templates variadiques

                Posté par . Évalué à 1.

                Je dis peut-être une bêtise, mais naïvement j’aurai mis en place un compteur par thread, et les opérations atomiques réservées aux moments vraiment nécessaires, tout aussi naïvement : quand on tombe à 0.

                • [^] # Re: templates variadiques

                  Posté par (page perso) . Évalué à 2.

                  Une référence n'appartient pas à un thread, donc ce que tu proposes ne peut pas marcher.

                • [^] # Re: templates variadiques

                  Posté par . Évalué à 3.

                  Euh non, là il s'agit de mémoire partagée entre plusieurs threads, je pense. Du coup tu as un compteur "global" qui compte le nombre d'utilisations dans tous les threads.

                  L'opération atomique doit se faire sur ce compteur global.

                  Par exemple, imagine qu'un thread prenne une référence à un objet qui a déja 10 références: on doit incrémenter le compteur et le passer à 11. Manque de bol le thread est interrompu pendant l'opération, un aute thread prend le relai et incrémente le compteur. L'opération de l'autre thread n'est pas finie et le compteur est toujours à 10 en RAM par exemple ... il passe à 11.

                  Retour au thread précédent : il est en pleine incrémentation du 10 à 11 et termine : il passe le compteur à 11.

                  Oups, on a donc 12 référence avec un compteur à 11. On doit absolument garantir que l'incrémentation ne soit pas interruptible en cours de route, c'est ce qu'on appelle une instruction atomique.

                  • [^] # Re: templates variadiques

                    Posté par . Évalué à 1.

                    On est bien d’accord que le mécanisme de compteur consiste à savoir le nombre de variables qui sont des pointeurs sur une zone de mémoire donnée ? Dans ce cas, la seule chose qui intéresse le GC est de savoir quand il n’y a plus aucune de ces variables, c.-à-d. lorsque le compteur arrive à 0. Je ne vois pas l’intérêt de synchroniser tous les threads à chaque incrémentation-décrémentation du compteur. Il y a seulement lorsqu’il arrive à 0 qu’il importe de mettre au courant le GC. Partant de là un comptage local est parfaitement envisageable.

                    • [^] # Re: templates variadiques

                      Posté par . Évalué à 1.

                      Relis attentivement l'exemple de Thomas. Si tu ne comprends pas pourquoi il faut un comptage de référence atomique, tu devrais t'abstenir de faire de la programmation multi-thread, tu vas au devant de sérieux problèmes.

                      • [^] # Re: templates variadiques

                        Posté par . Évalué à 4.

                        Je sais ce qu’est une opération atomique en multi-thread. Je pensais juste à un moyen de s’en passer.

                        abstenir de faire de la programmation multi-thread

                        J’en ai fait il y a deux ans. Les problème sont arrivés quand je me reposais de trop sur les opérations atomiques. J’ai systématiquement créé des zones mémoires exclusives à chaque thread pour résoudre les problèmes que j’avais avec les performances désastreuses des opérations atomiques. La synchro. entre les threads ne se réduisant qu’au strict nécessaire. C’était performant, au prix d’une conso. mémoire qui explosait :'(

                        Je ne connais rien en GC, c’est pourquoi je demande des précisions là-dessus. Par contre en parallèle je pense savoir où je mets les pieds, alors pas la peine de me faire la leçon.

                        • [^] # Re: templates variadiques

                          Posté par . Évalué à 1.

                          Désolé, jne n'avais pas cherché a comprendre ce que tu voulais dire, mais d'autre s'en sont chargés dans les commentaires an dessous. Maintenant je vois, et je me dis qu'un bon design d'utilisation des references partagées serait plus efficace que la solution a compteurs global/local. Mais mon, c'est que mon avis...

                    • [^] # Re: templates variadiques

                      Posté par . Évalué à 2.

                      J'ai oublié de préciser un truc dans mon exemple : les deux threads référencent la même zone mémoire, le même objet.

                      Dans ce cas tu ne peux avoir de compteur purement local, puisque plusieurs threads accèdent à la même zone mémoire. Dans ce cas il faut faire très attention dans la manipulation du compteur.

                    • [^] # Re: templates variadiques

                      Posté par . Évalué à 4.

                      En effet, je pense que les moinsseurs n'ont pas compris ta suggestion...
                      J'avais pensé à un truc dans le genre, basé sur des TLS.
                      En en fait il s'avère que d'autres y ont pensé, et même écrit :
                      http://www.iecc.com/gclist/GC-harder.html http://www.nntpnews.info/threads/4513004-Lock-free-reference-counting http://guillaume.segu.in/papers/ns3-multithreading.pdf et http://sysrun.haifa.il.ibm.com/hrl/ISMM2009/present/wild_2.pdf

                      Par contre, il y a quelques problèmes a prendre en compte :
                      - avec un refcount par thread, il faudrait au moins deux niveaux d'indirection supplémentaires (typiquement chaque thread aurait une table contenant la liste des refcounts de chaque objet, l'adresse de cette table étant retrouvée par TLS)
                      - cette indirection augmenterait significativement le nombre d'instructions nécessaires par incref/decref (le lookup TLS doit être rapide, mais derrière il faut retrouver le refcount correspondant à l'objet)
                      - surtout, tu perds énormément en localité de référence : dans le cas classique, ton refcount est très probablement déjà en cache puisque tu viens de toucher à l'objet (c'est l'un des intérêts du comptage de référence par rapport au rammase-miette, qui lui s'amuse souvent à suivre des objets qui ne sont pas en cache et à te dégager ceux qui y sont déjà). Quelqu'un avait suggéré il y a quelque temps de sortir le refcount des objets pour une autre raison (en gros, comme le refcount peut-être incrémenté même lorsque l'objet est lu, après un fork() tu perds vite l'économie de mémoire apportée par le COW pour des gros datasets, voir http://mail.python.org/pipermail/python-dev/2011-May/111562.html), la dégradation de performance était non-négligeable, et c'était juste avec un seul thread
                      - lorsque ton thread-local refcount tombe à 0, il vérifier que tous les autres thread-local refcounts pour cet objet sont à 0, et ceci de façon atomique
                      - enfin, j'ai comme dans l'idée que dans 90% des cas, le refcount par thread varie entre 0 et 1, du coup on ne gagnerait rien (en fait on perdrait même)

                      Mais je vais essayer de creuser ça quand j'aurai un peu de temps, ça pourrait être marrant...

                  • [^] # Re: templates variadiques

                    Posté par (page perso) . Évalué à 1.

                    C'est pas à ça que servent les variables volatiles ?

                    • [^] # Re: templates variadiques

                      Posté par (page perso) . Évalué à 7.

                      Non, le volatil permet d'indiquer au compilateur qu'il faut à chaque fois récupérer la variable en mémoire avant de faire des opérations (ne pas considérer que la bonne valeur a été lue et stockée dans un registre), par exemple sur les processeurs ou les I/O matérielles sont mappées dans les adresses mémoires et où les valeurs peuvent changer indépendamment de l'exécution du programme.

                      Mais ça ne garantit pas que deux taches concurrentes ne vont pas l'une après l'autre lire la même valeur dans la variable, faire l'opération d'incrémentation sur cette même valeur, puis la sauvegarder chacune à son tour (ie. 2 incrémentations réalisée, une seule visible).

                    • [^] # Re: templates variadiques

                      Posté par (page perso) . Évalué à 3.

                      Non, mais c'est lié. Une variable volatile permet de s'assurer que le compilateur ne va pas garder la variable dans un registre mais la relire à chaque accès. Par contre pour un accès donné, il faut quand même passer par un registre à un moment donné.

                      Par exemple, si x est une variable volatile et que tu as le code :

                      x = x + 1;
                      x = x + 1;
                      
                      

                      Le compilateur va charger x dans un registre, l'incrémenter et le sauvegarder en mémoire, puis recommencer. Si la variable n'est pas volatile, il va la charger une seule fois, l'incrémenter deux fois (une fois en fait, mais de deux...) puis la sauvegarder.

                      Donc si un autre thread modifie la variable entre la sauvegarde et le deuxième chargement, il n'y a pas de problème, par contre si le thread est interrompu pendant l'incrémentation cela merde quand même. Il faut une opération atomique pour éviter le problème.

                      • [^] # Re: templates variadiques

                        Posté par (page perso) . Évalué à 1.

                        D'après mes souvenirs d'assembleur x86, l'instruction d'incrémentation inc peut prendre soit un registre en paramètre, soit une adresse mémoire. L'opération x = x + 1 peut donc être nativement (i.e., sans mutex) atomique dans le cas des entiers.

                        Bien entendu, ce que je viens de dire n'est vrai que pour des processeurs ayant une instruction d'incrémentation acceptant en paramètre une adresse mémoire...

                        • [^] # Re: templates variadiques

                          Posté par (page perso) . Évalué à 6.

                          elle n'est atomique que si tes threads ne s'executent que sur un seul coeur , pour les situations où plusieurs coeurs accedent de manière concurrente à la mémoire il faut ajouter le préfix "lock" devant ton instruction, et c'est là que ça commence à couter cher. cf par exemple: http://www.codemaestro.com/reviews/8

                          • [^] # Re: templates variadiques

                            Posté par (page perso) . Évalué à 1.

                            Remarque fort judicieuse et merci de cette précision ! Il est vrai que je ne n'ai fait état de l'atomicité que du point de vue de la préemption, c'est-à-dire que l'opération commencée ne peut être interrompue par une autre, mais que je n'ai fait aucun cas de l'accès simultanée à la même zone mémoire (chose tout à fait possible dans le cas multicoeur). Merci donc de m'avoir corrigé.

                        • [^] # Re: templates variadiques

                          Posté par (page perso) . Évalué à 3.

                          Sauf que les processeurs modernes sont tous pipeliné, donc entre le moment ou la valeur est lue et le moment ou elle est écrite il se passe plusieurs cycles même si elle est indiquée s'exécuter en un seul cycle.
                          Donc même avec deux cœur sur un seul processeur, tu peux avoir des problèmes

                          thread 1       | thread 2
                          lecture val    | ...
                          incrementation | lecture val
                          ecriture val   | incrementation
                          ...            | ecriture val
                          
                          

                          Ton opération n'est pas atomique si tu ne met pas le préfixe lock qui va blocker toutes les lectures de la valeur sur tous les processeurs.
                          • [^] # Re: templates variadiques

                            Posté par (page perso) . Évalué à 1.

                            Comme je le disais juste au dessus, je ne faisais état de l'atomicité que du point de vue de la préemption sans tenir compte des accès concurrents (et c'est mon tord, je le reconnais)

                      • [^] # Re: templates variadiques

                        Posté par (page perso) . Évalué à 2.

                        • [^] # Re: templates variadiques

                          Posté par (page perso) . Évalué à 3.

                          Oui, mais non :-)

                          Tout d'abords cet article s'applique au code kernel ou tu dispose de primitive de synchro bas niveau. Pour du code portable, dans le cas de l'attente active dont il parle par exemple, un accès volatile est plus portable.

                          Ensuite, même si tu dispose de ces primitives, il y a pas mal de cas ou il est préférable d'utiliser volatile. Toutes les primitives en question vont placer une "full memory barier" ce qui oblige le compilateur à bloquer toutes les optimisation en avant et en après. Une variable volatile ne bloque les optimisations que pour cette variable, ce qui peut-etre très utile dans le cas ou tu n'as pas besoin d'un lock.

                          Par exemple, dans le cas d'une table de hashage (presque) lock-less, un thread qui écris une valeur doit mettre à jour deux case mémoires. Un thread qui veux lire cette valeur, doit faire une attente active sur la deuxième au cas ou le premier thread ai été interrompu entre les deux écritures. Un accès volatile est beaucoup moins couteux que la solution proposée.

                      • [^] # Re: templates variadiques

                        Posté par (page perso) . Évalué à 2.

                        Merci pour vos précisions.

              • [^] # Re: templates variadiques

                Posté par (page perso) . Évalué à 5.

                Pas de bol, ça reste généralement plus efficace que beaucoup d'autres méthodes.

                Avec un GC stop-the-world du style mark-and-sweep, tu dois stopper tous les threads à chaque fois que le collecteur ce met en route, au final c'est pas forcément mieux. De plus, la grosse majorité des objets ne changent pas de threads, donc avec un refcount, tu ne passe au opération atomiques que pour une partie des objets. Avec un stop-the-world, tu stop toujours tout le monde.

                Avec un GC on-the-flow ou incrémental tu te tapes aussi des opérations atomiques et pour certains elles sont même plus lourdes que pour le refcount.

                Bref, le refcount reste un bon algol de GC qu'il faut toujours considérer avant de s'engager dans des choses plus compliquées, même dans le cas du multi-threading.

                • [^] # Re: templates variadiques

                  Posté par . Évalué à 4.

                  Le ref count a le problème immense de rajouter une opération dés que tu fais une affectation, ce qui a offre une grosse pénalité. Ce n'est pas pour rien que aucune VM n'utilise de ref count(sauf perl). Dans le cas avec ref count, tu fais des opération en plus à chaque affectation et tu désalloues la mémoire lors de la perte d'une référence (compteur à zéro). Dans les autres cas, il n'y aucune opération en plus dans ses 2 cas. Dans beaucoup d'allocateurs mémoire rapide, le "free" doit se faire sur un grand nombre d'objet pour gagner beaucoup de temps (allocateur d'apache par exemple).

                  Dans les gc modernes, ils sont souvent générationnels avec d'autres zones mémoire et d'autres algo en fonction de l'age des données. Dans ocaml, il y a un GC par copie sur une petite zone (250ko ?, cela permet aussi de lutter contre la fragmentation), puis un mark&sweep (de mémoire).

                  "La première sécurité est la liberté"

                  • [^] # Re: templates variadiques

                    Posté par (page perso) . Évalué à 9.

                    Reprend mon premier commentaire et tu y verras :
                    >Le comptage de référence est loin d'être mauvais et est même parfais dans certains cas. C'est juste qu'il n'est adapté que à certains cas d'utilisation et pas au cas général.
                    Je précise bien "pas au cas général". Par contre, dans le cas d'utilisation courant des smart pointers, c'est parfait.

                    Je répondais surtout à la critique contre le comptage des référence qui serait "le degré zéro" des GCs avec des performances pourries.
                    Ce n'est pas le cas, et il y a de nombreuses situations ou un simple comptage de référence est beaucoup plus efficace, simple à implémenter et souvent moins buggué que n'importe quel autre type de GC.

                    Pour continuer sur les différents GCs :

                    Pour ce qui est des opérations à chaque affectations, n'importe quel compilateur pas trop con va te permettre d'en supprimer une grande partie, et au final il n'en reste pas forcément tant que cela. Et tu as l'avantage que tu n'as jamais besoin de stopper tous tes threads afin de faire tourner ton GC une fois de temps en temps.

                    Que ton GC soit générationel ou pas ne change rien, l'aspect générationel ne va te permettre que de réduire la liste des objets à parcourir lors d'un cycle de GC, mais tu devra quand même tout stopper pour le faire. Si ton GC est capable de ne pas stopper le monde pendant qu'il tourne, alors il va nécessiter lui aussi des opérations pendant les affectations.

                    Ce qui rend le comptage de référence peu intéressant dans le cas général c'est que pour ne pas avoir trop d'opérations à faire à chaque affectation et à chaque fin de bloc, il est nécessaire de complexifier le compilateur et de le rendre plus intelligent sur ces éléments. Pour peu que tu ne sois pas dans un cas ou les cycles peuvent être évités ou détecter simplement, il faut aussi les prendre en compte.
                    Au final, le risque est d'avoir une implémentation aussi complexe qu'un autre GC mais en plus très liées au cœur du compilateur ce qui la rend encore plus complexe. Alors qu'un GC de type mark-and-sweep sera indépendant et donc plus simple à debugger.

                    Si tu peut te permettre des petites pause de temps en temps, il n'y a pas de problèmes avec d'autres formes de GC. Ce n'est pas pour rien qu'il y a autant de recherche sur les différentes manières de réduire le temps de pause à chaque déclenchement.

                    J'ai bossé sur DSL ou le comptage de références était vraiment l'algo optimal. Les structures de données disponibles rendaient les cycles très simple à détecter, les manipulations sur les objets ce prêtaient bien au comptage, par contre les contraintes de timing ne permettaient pas de stopper tous les threads de manière aléatoire. Le mec qui à essayer de remplacer mon GC par un truc plus évolué n'a jamais réussi à tenir les contraintes.

                    En conclusion :

                    Il ne faut pas utiliser bêtement le comptage de référence mais il faut pas cracher dessus non-plus. Il est adapté à certains cas, et c'est notamment souvent le cas quand on utilise les smart pointers.

                    • [^] # Re: templates variadiques

                      Posté par . Évalué à 2.

                      On retourne dans les cas d'utilisation de plusieurs algo, selon les cas. J'avais pourtant lu que les algo fait avec des smart pointer n'avait pas les même performances qu'avec des langages avec un GC.

                      Concernant la nécessité de basse latence, il me semblait que c'était le but des mark&sweep incrémental. Il me semble aussi que linux utilise un système de parcours rapide pour son algorithme de défragmentation de la mémoire réelle (pour faire de la place pour les huge page)

                      "La première sécurité est la liberté"

                      • [^] # Re: templates variadiques

                        Posté par (page perso) . Évalué à 9.

                        Le truc c'est qu'il faut comparer ce qui est comparable. Si tu remplaces un GC complet par des smart pointers tu vas clairement perdre en performances.

                        Les smart pointers ne sont absolument pas fait pour gérer tous les pointeurs d'un programme à la manière d'un langage à GC. Ils servent à gérer les quelques pointeurs au sein de l'application ou c'est pratique. Plus la proportion de pointeurs que tu veux gérer de cette manière est grande, moins les perfs seront compétitives.

                        Le C++ est un langage fait pour la gestion manuelle de la mémoire avec des outils pour qu'elle soit automatique de temps en temps, et dans ce cas les smart pointeurs sont beaucoup plus efficace car cela donne un outils simple et efficace beaucoup moins lourd qu'un GC complet.
                        Si tu utilise de manière systématique les smart pointers, le problème est plus que le langage choisit n'est pas le bon.

                        Pour ce qui est de la latence, les algos incrémentaux sont intéressant, mais ils viennent avec un prix : ils sont très complexes à mettre en œuvre, ils demandent beaucoup de coordination avec le runtime, et beaucoup de tuning de paramètres pour être vraiment efficaces. Un petit changement anodin peu faire tout écrouler.

                        Pour la défragmentation dans linux, je ne suis jamais aller voir ce qui ce faisait exactement, mais on est dans le cas d'une tâche bien définie ou typiquement la fréquence d'utilisation des objets est très grande et ou la collection est peu fréquente. Si tu as besoin de quelques huges pages, ton algo ne va tourner que le temps d'avoir ces pages et jamais faire de cycle complet.
                        Dans un cas comme celui là, tu peux généralement choisir des algos à priori complexes mais qui en fait deviennent très simple car il n'y a qu'un seul type d'objets avec un comportement extrêmement simple.

                      • [^] # Re: templates variadiques

                        Posté par (page perso) . Évalué à -4.

                        Ce que je ne comprends pas, c'est que tu te ramène avec des histoire de GC alors qu'on parle d'un objet RIAA. Pour ainsi dire, ça n'a pas grand chose à voir.

                        Un GC gère l'allocation et la désallocation coordonnée d'objets.
                        Un RIAA ne gère que la libération d'une ressource.

                        • [^] # Re: templates variadiques

                          Posté par (page perso) . Évalué à 3.

                          Non, un GC gère la libération lui aussi. Il doit détecter quand un objet n'est plus utilisé et le libérer. (éventuellement avec un décalage) Par contre, pour faire cela il a souvent besoin de la coopération du code responsable de l'allocation (et des copies, ...) tout comme un smart pointer.
                          Le constructeur du smart pointer peut très bien être vu comme l'allocation de la ressource, c'est un moyen de signaler qu'un pointeur doit être gérer et un moyen d'être tenu au courant des nouvelle références sur ce pointeur.

                          Il est tout à fait possible même si c'est stupide d'implémenter les smart pointer avec n'importe quel algo de GC.

                          • [^] # Re: templates variadiques

                            Posté par . Évalué à 3.

                            Non, un GC gère la libération lui aussi. Il doit détecter quand un objet n'est plus utilisé et le libérer. (éventuellement avec un décalage)

                            Le décalage (ou retard) fait en pratique une grosse différence sur certains types de ressources (par exemple les sockets, ou les fichiers sous Windows).

                            • [^] # Re: templates variadiques

                              Posté par (page perso) . Évalué à 1.

                              Et le rapport avec la choucroute, ce n'est pas un problème du GC mais du destructeur qui n'est pas approprié à la gestion automatique de la mémoire ?

                              Sans compter que les exemples que tu donnes ne sont en rien spécifique à Windows mais sont présent quels que soit l'OS. Un socket, tant qu'il n'a pas été fermé, il reste ouvert... C'est même pour cela que sur les systèmes à GC, il y a une méthode pour les fermer explicitement à un instant donné sans attendre que le destructeur soit appelé par le GC.

                              Dans beaucoup de cas, le GC fera très bien sont boulot sur ces cas là aussi, il n'y a que lorsque tu dois ouvrir beaucoup de ressources et que le nombre de ressource pouvant être ouvertes à un instant donné est limité que cela pose problème.

                              • [^] # Re: templates variadiques

                                Posté par (page perso) . Évalué à -1.

                                Dans l'absolu, il ne faudrait jamais laisser un objet RIIA libérer tout seul ces ressources : il vaut mieux explicitement la fonction de libération. En tout cas, en C++ c'est obligatoire si la libération de la ressource est susceptible de lever une exception (comportement indéfinis lors de la levée d'une exception dans un destructeur).

                                • [^] # Re: templates variadiques

                                  Posté par . Évalué à 3.

                                  Le but du RIIA est justement de ne pas avoir à se soucier de la destruction.

                                  "La première sécurité est la liberté"

                                  • [^] # Re: templates variadiques

                                    Posté par (page perso) . Évalué à 2.

                                    Non, le but n'est pas d'automatiser la destruction : c'est d'éviter la fuite de ressources.

                                    Par exemple, si tu as un objet RAII qui, lors de sa destruction, risque d'émettre une exception, le comportement sera indéfini car l'exception sera levée dans le destructeur de l'objet RAII.

                                    Donc il faut toujours explicitement libérer la ressource pour être certain d'avoir un comportement défini.

                                    • [^] # Re: templates variadiques

                                      Posté par . Évalué à 2.

                                      Utiliser un objet RAII qui peut lever une exception n'a aucun sens dans ce cas. Le but est d'utiliser le même genre de gestion de destruction pour les périphériques (socket, fichier) que pour la mémoire.

                                      Resource Acquisition Is Initialization, often referred to by the acronym RAII, is a programming idiom used in several object-oriented languages like C++, D and Ada. The technique was invented by Bjarne Stroustrup[1] to deal with resource deallocation in C++.
                                      https://secure.wikimedia.org/wikipedia/en/wiki/Resource_Acquisition_Is_Initialization

                                      "La première sécurité est la liberté"

                                      • [^] # Re: templates variadiques

                                        Posté par (page perso) . Évalué à 4.

                                        Wikipedia n'est pas une source.

                                        Je crois que tu ne comprends pas le problème ici.

                                        Lever une exception dans un destructeur mène a un comportement indéfini (ton programme se retrouvera dans un état impossible à prédire).
                                        L'appel à std::fstream::close peut lever une exception en cas d'échec http://www.cplusplus.com/reference/iostream/fstream/close/

                                        Donc, laisser l'objet gérer la fermeture du fichier peut mener à un comportement indéfini.

                                        Dans le cas de std::shared_ptr<>::release, c'est moins problématique. En effet, un destructeur devrait être nothrow. Maintenant, imagine le combo :

                                        std::shared_ptr<std::ifstream> pif(new std::ifstream("a_file")) ;
                                        
                                        

                                        Si la fermeture du fichier lève une exception pour une raison quelconque, quel sera l'état du programme pour peut qu'on attrape les exceptions quelque part ?

                                        L'intérêt d'un RIAA, c'est que la ressource ne va pas fuiter. Par contre, ça ne garanti pas que le programme va correctement fonctionner si le destructeur n'est pas exception-free.

                                        J'aimerais bien comprendre pourquoi je suis moinsé alors que je corrige une erreur de débutant.

                                        • [^] # Re: templates variadiques

                                          Posté par . Évalué à 2.

                                          Par ce que tu parles d'une contrainte de C++ qui fait que mettre un close dans un destructeur est une mauvaise idée, alors que l'on parle du principe de RAII, qui est de faire gérer une ressource par le scope classique des objet. Cela n'a juste pas grand chose à voir.

                                          "La première sécurité est la liberté"

                                          • [^] # Re: templates variadiques

                                            Posté par (page perso) . Évalué à 2.

                                            Je crois que l'article sous lequel nous trollons, concerne C++.

                                            Cependant, je ne suis pas certain que ce problème d'exception levée dans un destructeur soit spécifique à C++. C'est une patate chaude que la norme avait balancé dans le domaine du comportement indéfini, mais il faut bien avouer qu'il n'est pas évident à apporter une réponse claire et nette à cette question : « quel est l'état d'un objet en court de destruction qui voit se lever une exception durant sa destruction ? » On aura un objet pas complètement détruit qui risque de pourrir l'état du programme : que doit-on faire, à part paniquer et tuer le programme ?

                                        • [^] # Re: templates variadiques

                                          Posté par . Évalué à 4.

                                          Grosse parenthèse: close lèvera une exception uniquement si le flux est configuré pour lever des exceptions. Ce n'est pas le défaut.
                                          Petite parenthèse, c'est RAII, pas RIAA (c'est pas l'asso de majors du disque ça ?), voire même RRIF (Resource Release is Finalization) si on veut se la jouer pédantique et confiner le RAII à son emploi premier. Bref.

                                          Dans l'absolu tu as raison. Il y a un risque en cas de restitution problématique de ressource quand un échec est plausible. C'est pour cela qu'il est exigé qu'un destructeur ne doive jamais lever d'exception. Et encore, il me semble avoir vu des changements à ce sujet dans le prochain standard. Il faudrait que je remette la main dessus.

                                          Maintenant, le "je ne peux pas fermer le fichier/la socket", honnêtement, cela passe toujours à la trappe, car même si on ne peut pas le fermer, on ne peut rien en faire non plus, et je ne connais pas de programme qui le gère correctement, même avec codes de retour. Des noyaux/bibliothèques peut-être pour faire remonter l'information jusqu'à l'appelant, qui bien souvent n'en fera rien.

                                          Mon expérience est qu'ignorer une restitution refusée est généralement moins grave (et encore moins probable) que de laisser s'échapper une ressource. D'où que : le RAII suffit amplement. Que les débutants commencent par apprendre à s'en servir, on passera à l'étape suivante après.

                              • [^] # Re: templates variadiques

                                Posté par . Évalué à 3.

                                Sans compter que les exemples que tu donnes ne sont en rien spécifique à Windows mais sont présent quels que soit l'OS.

                                Les fichiers sous Windows, c'est particulier. Tu ne peux pas les effacer s'ils sont ouverts par un autre processus, par exemple. Donc fermer un fichier trop tard peut être très chiant pour l'utilisateur.

                          • [^] # Re: templates variadiques

                            Posté par (page perso) . Évalué à 1.

                            Le GC s'occupe de gérer de la mémoire, allocation et désallocation, comme je l'ai noté. La notion fondamentale est qu'ici on compare un outil de la gestion de la vie d'objets en mémoire (un GC), avec un gestionnaire de libération de ressource (quelconque). Oui, on peut utiliser std::shared_ptr pour autre chose qu'un pointeur sur le tas, il suffit de spécialiser le libérateur.

                            Le constructeur du smart pointer ne peut pas être vu comme l'allocateur de ressource, pour la simple et bonne raison qu'il n'alloue rien du tout. Ce n'est pas sa responsabilité.

                            #include <memory>
                            std::shared_ptr<int> pipi ; // N'alloue pas un int
                            
                            
                            • [^] # Re: templates variadiques

                              Posté par (page perso) . Évalué à 1.

                              Si, il va allouer quelque chose : un objet (au sens général du terme) qui est managé et qui gère les accès à ton int.

                              Il faut bien comprendre qu'un smart pointer est une enveloppe autour d'un pointer, et que ce qui est gérer automatiquement c'est cette enveloppe. Elle à pour but de détecter à quel moment son contenu n'est plus utiliser et d'effectuer une action sur le contenu à ce moment là.
                              C'est exactement le même principe qu'un GC. Si tu veux te convaincre qu'il va bien y avoir une allocation, il suffit de ce demander ou va être stocker le compteur de référence.

                              • [^] # Re: templates variadiques

                                Posté par (page perso) . Évalué à 1.

                                Ben non, le constructeur de std::shared_ptr n'alloue pas d'objet. Le constructeur par défault créé un smart pointer invalide.

                                #include <memory>
                                int main()
                                {
                                  std::shared_ptr<int> pipi ;
                                  *pipi = 42 ; // Comportement indéfini car *pipi pointe sur rien
                                  // pas d'exception parce que ça ne correspond pas à la sémantique des pointeurs
                                }
                                
                                
                                • [^] # Re: templates variadiques

                                  Posté par (page perso) . Évalué à 2.

                                  Tu fais exprès de ne pas comprendre ?

                                  Un smart pointer est un objet qui encapsule un pointeur, il y a donc bien une allocation de l'objet qui encapsule. Tu peux mettre ce que tu veux comme pointeur dedans, qu'il soit valide ou invalide ne change rien au fais qu'il y a bien allocation d'une coquille pour stocker le pointeur et son compteur de références.

                                  Et pour revenir à ta remarque de départ c'est exactement ce que fais un GC : une nouvelle ressource est allouée et signalée au GC, lequel va placer la ressource dans une enveloppe avec les infos dont il a besoin pour pouvoir libérer la ressource quand ce sera nécessaire.

                                  La seule différence est que souvent le GC va gérer tous les objets qui peuvent être alloués donc plutôt que d'avoir une enveloppe explicite, elle est implicite, c'est-à-dire que les objets intègrent eux-même les informations du GC afin d'éviter l'indirection et la surcharge en mémoire.
                                  Et dans le cas des smart-pointers c'est la même chose car avec une bonne libc++ et un bon compilateur l'enveloppe en question est réduite à un compteur de référence avec un binding implicite au pointeur.

                                  Donc, pour reprendre ton commentaire initial :
                                  >Un GC gère l'allocation et la désallocation coordonnée d'objets.
                                  >Un RIAA ne gère que la libération d'une ressource.
                                  Soit tu ne considère pas que l'allocation de la coquille fais partie de l'allocation de l'objet, et donc ta première phrase est fausse ; soit tu considère qu'elle en fais partie, et dans ce cas c'est la deuxième qui est fausse.

                                  Un GC et un smart pointer font la même chose : enregistrer l'existence d'une ressource et la libérer quand elle n'est plus nécessaire.

                                  • [^] # Re: templates variadiques

                                    Posté par . Évalué à 1.

                                    Un smart pointer est un objet qui encapsule un pointeur, il y a donc bien une allocation de l'objet qui encapsule. Tu peux mettre ce que tu veux comme pointeur dedans, qu'il soit valide ou invalide ne change rien au fais qu'il y a bien allocation d'une coquille pour stocker le pointeur et son compteur de références.

                                    La derniere fois que j'ai regardé l'implementation de boost::shared_ptr, j'y ai vu... qu'il n'y a pas de telle coquille. Le shared_ptr possède deux membres, un pointeur vers le compteur de référence, et un pointeur vers l'objet partagé (oui, les deux ne sont pas co-localisés, c'est un design possible, il y avait un papier sur les performances qui trainait sur le site, bref...)

                                    Et avec ce modèle, on n'est pas obligé d'allouer le compteur dans le constructeur par défaut, parce que cela ne sert a rien.

                                    • [^] # Re: templates variadiques

                                      Posté par . Évalué à 1.

                                      il va pinailler en disant qu'il y a une allocation de deux pointeurs (mais pas d'objet pointés)

                                      Il ne faut pas décorner les boeufs avant d'avoir semé le vent

                                      • [^] # Re: templates variadiques

                                        Posté par . Évalué à 1.

                                        Oui, effectivement, il y a allocation de deux pointeurs sur la pile (pour une définition étendue de l'allocation). Mais bon, c'est la pile quoi, y'a pas besoin de GC ...

                                        • [^] # Re: templates variadiques

                                          Posté par (page perso) . Évalué à 3.

                                          Sauf que le pointeur qui est gérer par cette allocation n'est pas sur la pile lui. (sinon il n'y aurait pas besoin d'un smart-pointer)

                                          Donc pour recentrer la discution sur le point de départ une fois de plus : ma remarques de départ indiquait juste qu'un smart pointer et un GC interviennent de la même façon au moment ou l'objet deviens managé.

                                          Que les données servant à ce que la mémoire soit manager soit sur la pile, dans le tas, ou sur mars, ne change rien au fait qu'ils ont le même comportement, contrairement à ce que disait LupusMic.

                                          Je pense que la confusion viens justement de ce problème de mémoire et d'allocation. Il faut bien séparer deux chose ici : la mémoire pointée et la coquille. Les deux doivent être alloué pour que tout ce passe bien.
                                          Dans le cas du smart pointer, la mémoire pointé est alloué par l'utilisateur, et la coquille implicitement par la classe smart pointer.
                                          Dans le cas du GC, c'est la même chose : le compilateur alloue la mémoire pour l'objet demandé par l'utilisateur et ensuite informe le GC de cette zone de mémoire pour qu'il mette en place sa coquille autour.

                                          Dans le cas du smart pointer, le compilateur est en général suffisament intelligent pour supprimer l'indirection de pointeur et juste alloué un compteur sur la pile ou le tas suivant le type de smart pointer.

                                          Dans le cas du GC, vu que les chose sont plus complexe, on prévoit le coup avant et on bouge les infos du GC directement dans l'objet. Mais, ce n'est pas le GC qui va alloué la mémoire pointée.

                                          Si on veut simplifier, dans les deux cas l'utilisateur fait l'appel à malloc (pour ces données) et demande au GC de ce charger de l'appel à free.

                                    • [^] # Re: templates variadiques

                                      Posté par (page perso) . Évalué à 2.

                                      Tu relis mon commentaire puis le tiens et tu verras que tu viens de confirmer ce que j'ai dis...

                                      La classe boost::shared_pointer c'est quoi ? Une coquille autour de ton pointeur... Et cette coquille est faite justement pour pouvoir enregistrer les nouvelles références et leur disparitions afin de libérer la ressource quand elle n'est plus utilisée.

                                      Et avec ce modèle, on n'est pas obligé d'allouer le compteur dans le constructeur par défaut, parce que cela ne sert a rien.

                                      Il y a deux cas possibles :
                                      - soit tu permet de changer la valeur du pointeur encapsulé, et dans ce cas il vaux mieux que ton compteur soit alloué des le début sinon tu vas avoir du mal à le rajouter plus tard ;
                                      - soit tu ne permet pas de changer la valeur du pointeur et dans ce cas tu retombes à nouveau dans le même cas qu'un GC : lui non plus ne met rien autour des "atom" car il n'y a pas de libération à gérer.

                                      Donc, on en reviens toujours au fait qu'un smart pointeur fait la même chose qu'un GC.

                                      • [^] # Re: templates variadiques

                                        Posté par . Évalué à 1.

                                        • soit tu permet de changer la valeur du pointeur encapsulé, et dans ce cas il vaux mieux que ton compteur soit alloué des le début sinon tu vas avoir du mal à le rajouter plus tard ;

                                        Eh bien visiblement, les auteurs de boost::shared_ptr n'ont pas fait ce choix, le compteur n'est pas alloué dans le cas du constructeur par défaut, et pourtant on peut changer la valeur du pointeur encapsulé par affectation d'un autre shared_ptr, ou appel de la fonction reset(T*).

                                        C'est l'absence d'allocation qui permet de garantir que le constructeur par défaut ne lance jamais d'exception (nothrow guarantee), et c'est important pour l'implementation de la fonction membre reset() qui utilise ce constructeur (cf la documentation).

                                        Et on verifie tout ca dans le source :

                                        http://www.boost.org/doc/libs/1_47_0/boost/smart_ptr/shared_ptr.hpp
                                        http://www.boost.org/doc/libs/1_47_0/boost/smart_ptr/detail/shared_count.hpp

                                        • [^] # Re: templates variadiques

                                          Posté par (page perso) . Évalué à 1.

                                          Sauf que...

                                          1) Le compteur de référence est stocké dans la coquille par une indirection, en gros tu as un pointeur vers le compteur. Ça simplifie pas mal de choses, mais au final il y a bien toujours une coquille... Et la différence entre allouer un compteur ou un pointeur...

                                          2) On s'en contretape de ce qui est alloué au final: la seule chose qui compte dans la discussion en cours c'est qu'il y a bien un coquille de construite autour de ton pointeur, ce qui implique que le smart pointer ce comporte exactement de la même manière qu'un GC.

                                          3) On s'en contretape même qu'il y ait une allocation ou pas, ce qui est important c'est que tu viens une fois de plus de donner des arguments qui prouvent que tu avais tord.

                                          J'hésite entre deux opinions :
                                          - soit tu ne comprends vraiment pas et dans ce cas je te conseil de relire le thread depuis ton post qui à déclencher ma première réponses ;
                                          - soit tu as compris que tu avais tord mais tu ne veux pas l'admettre, et dans ce cas la ça explique pourquoi tu continues de parler de truc qui n'ont rien à voir à ma remarque initiale.

                                          • [^] # Re: templates variadiques

                                            Posté par . Évalué à 1.

                                            N'hesite plus, je ne comprends vraiment pas, en particulier le sens que tu donnes au verbe "allouer". Mais, ce n'est pas grave, tu as raison et j'ai tord (et je m'en contretape).

                                            • [^] # Re: templates variadiques

                                              Posté par (page perso) . Évalué à 4.

                                              Mais, ce n'est pas grave, tu as raison et j'ai tord (et je m'en contretape).

                                              sauf que mon objectif n'est pas d'avoir raison mais que l'on ce comprenne.

                                              Je donne au verbe "alloué" le sens de "réservation de mémoire". De la mémoire qui peut être sur le tas, dans la pile, ou même dans un registre. Quand tu déclare une variable ou un objet, tu as une allocation, de même si tu fais un appel à malloc ou a new.

                                              Mais dans tous les cas, l'allocation n'est pas ici l'élément pertinent. Ce qui est pertinent c'est que tu as au départ un pointeur, et que tu enveloppe ce pointeur dans une classe *_ptr pour ne pas avoir à te soucier de le libérer manuellement.
                                              Dans ce cas, ta classe *_ptr réalise exactement la même chose qu'un GC, détecter quand le pointeur n'est plus utilisé et le signaler au gestionnaire de mémoire.

                                              Quand tu crées un *_ptr, tu ne fais que signaler que l'objet doit être manager.
                                              Dans un langage à GC, quand un objet est créé, il est de même signalé au GC.
                                              -> La seule différence est que dans le cas d'un langage à GC c'est fais implicitement car tous les objets sont managés.

                                              Quand il n'y a plus de références, le destructeur du *_ptr va appeler un éventuel finaliseur puis signaler au gestionnaire mémoire que le pointeur peut-être libéré.
                                              Dans un langage à GC, quand il n'y a plus de références, le GC va appeler un éventuel finaliseur puis signaler au gestionnaire de mémoire que le pointeur peut-être libéré.
                                              -> Aucune différences à part le fait que certains GC ne supportent pas les finaliseurs.

                                              Donc, je ne comprend pas ou tu vois une différence entre les *_ptr et un GC. Les *_ptr sont des GC qui ne managent qu'une partie des objets et qui imposent la présence de finaliseurs. Leur définition fais que tous les algorithmes de GC ne peuvent être utilisés simplement, mais ça reste un GC.

                                              • [^] # Re: templates variadiques

                                                Posté par . Évalué à 2.

                                                Donc, je ne comprend pas ou tu vois une différence entre les *_ptr et un GC.

                                                Oui, tu as raison, les deux ont le meme objectif. Il n'y a pas plus de différence qu'entre FILE* et std::iostream.

                                                • [^] # Re: templates variadiques

                                                  Posté par (page perso) . Évalué à 3.

                                                  En effet, il n'y a pas le même niveau d'abstraction par rapport au formes de GC les plus communes.

                                                  Mais, tous les GC ne sont pas globaux, il y a beaucoup de cas où le GC ne gère qu'une partie de la mémoire, et dans ces cas là, l'utilisation ressemble énormément au smart pointers.

                                                  Un premier exemple : j'ai bossé avec un DSL où la majorité des types sont atomiques et les objets sont par défaut non-managés et utilisés par référence. Lorsque tu n'as plus besoin d'un objet tu fais un "delete(monobjet)" dessus et la référence deviens un atome représentant un pointeur invalide.
                                                  Ce DSL possède en plus un GC utilisé principalement pour les objets qui ont une longue durée de vie où qui passent d'un thread à l'autre. Dans ce cas, tu fais un appel "gc.manage(monobjet)" et tu n'as plus besoin de t'occuper du "delete(monobjet)".
                                                  On a bien un GC, de type mark-and-sweep dans ce cas là, qui s'utilise comme un smart pointer et c'est relativement courant comme système.

                                                  Deuxième exemple : Le Boehm-GC est un GC pour le C de type mark-and-sweep lui aussi et qui lui aussi ne va manager que la mémoire que tu lui demandes de manager.
                                                  Le cas d'utilisation typique c'est, si tu dois allouer de grosses quantités de mémoire qui ne peuvent contenir de références et dont la durée de vie correspond à celle du programme. C'est loin d'être un cas rare : une base de donnée en lecture seule, ou un modèle pour du calcul numérique. Dans ce cas, tu ne manage pas cette mémoire car sa durée de vie est bien connue et tu ne veux pas que le GC perde du temps à scanner cette mémoire à la recherche de référence qui n'y seront pas.

                                                  Il n'y a que dans les langages qui intègrent complètement le GC et où tous les objets sont managés que le GC est vraiment plus abstrait que le smart_pointer. En quantité de code qui tourne, ça représente largement la majorité rien qu'avec Java et la majorité des langages de script. Par contre en nombre d'implémentation de GC, je ne suis pas sur que ce soit le cas le plus courant.

                                                  Au final un long thread juste à cause d'une correction d'une petite erreur...

                                  • [^] # Re: templates variadiques

                                    Posté par (page perso) . Évalué à 1.

                                    Tu fais exprès de ne pas comprendre ?

                                    Un smart pointer est un objet qui encapsule un pointeur, il y a donc bien une allocation de l'objet qui encapsule. Tu peux mettre ce que tu veux comme pointeur dedans, qu'il soit valide ou invalide ne change rien au fais qu'il y a bien allocation d'une coquille pour stocker le pointeur et son compteur de références.

                                    Tout d'abord, je n'avais effectivement pas compris que tu parlais du détail d'implémentation (la coquille) pour argumenter que le smart pointer faisait une allocation à l'instantiation du shared_ptr. C'est un détail d'implémentation qu'il fait une allocation mémoire. On pourra imaginer un smart_pointer qui utilisera une zone statique, et dans ce cas là tu n'auras pas d'allocation comme tu l'entends.

                                    En fait, ce que je comprenais de tes explications, c'est que je pensais que tu croyais que le smart pointer alloue un objet par défaut. Ce qui serait une erreur de conception, puisque tout les objets n'ont pas un constructeur par défaut explicite.

                              • [^] # Re: templates variadiques

                                Posté par . Évalué à 1.

                                (je réponds ici car les 150 niveaux d'indentation deviennent illisibles)
                                GC et smart-pointers, il y a quand même des différences:
                                - Un GC, maintenant, sait gérer tout seul les cycles ; à contrario des pointeurs à comptage de référence, avec lesquels il faut s'aider de weak_ptr ou assimilé
                                - Un GC ne finalise (comprendre: "détruit") pas l'objet pointé à contrario de tous les smart-pointers du C++.
                                - Tous les smart pointers ne sont pas à comptage de référence. La veille définition, c'est qu'un pointeur non brut/nu est smart/intelligent. Ainsi std::auto_ptr est un smart-pointer, tout comme boost::scoped_ptr, ou std::unique_ptr. [J'ai un peu de mal à voir pourquoi ces pinaillages autour des compteurs du coup]
                                - Les *_ptr<> de boost (et de C++11) peuvent encapsuler autre chose que de la mémoire, et ça c'est classe: nous avons ainsi des scoped_guard. Un GC ? Même pas en rêve. Il ne connait que la mémoire.
                                - Après je me souviens de discussions sur fclc++ ou clc++m où un avantage était prêté aux GC : le MT car ils n'utilisaient pas de compteurs atomiques.

                                OK, ils ont une fonction commune : la restitution implicite de la mémoire quand plus rien n'y fait référence ; de suite pour les smart pointer (déterministe), un jour pour les GCs (non déterministe). Et c'est tout. Sorti de ça, ce n'est pas du tout pareil.

                                • [^] # Re: templates variadiques

                                  Posté par (page perso) . Évalué à 2.

                                  • Un GC, maintenant, sait gérer tout seul les cycles ; à contrario des pointeurs à comptage de référence, avec lesquels il faut s'aider de weak_ptr ou assimilé

                                  Un GC par comptage de référence de base non plus ne sait pas gérer les cycle, et c'est aussi le cas d'autres algorithmes. On pourrait très bien ajouter cette fonctionnalité aux smart-pointers, mais vu leur cadre d'utilisation ce ne serait pas très utile.

                                  • Un GC ne finalise (comprendre: "détruit") pas l'objet pointé à contrario de tous les smart-pointers du C++.

                                  Les smart pointer non-plus ne finalisent pas les objets, ils font comme le GC : quand un objet n'est plus référencé, ils le signalent au gestionnaire de mémoire qui va s'occuper de la libération.

                                  • Tous les smart pointers ne sont pas à comptage de référence. La veille définition, c'est qu'un pointeur non brut/nu est smart/intelligent. Ainsi std::auto_ptr est un smart-pointer, tout comme boost::scoped_ptr, ou std::unique_ptr. [J'ai un peu de mal à voir pourquoi ces pinaillages autour des compteurs du coup]

                                  Tous les GC ne sont pas non-plus à comptage de référence, on ne parlait du compteur que pour illustrer le fait qu'il y a bien une enveloppe créé autour du pointeur.

                                  • Les *_ptr<> de boost (et de C++11) peuvent encapsuler autre chose que de la mémoire, et ça c'est classe: nous avons ainsi des scoped_guard. Un GC ? Même pas en rêve. Il ne connait que la mémoire.

                                  La majorité des GC te permet de le faire. Quasiment tous les GC permettent de spécifier un "destructeur" pour les objets : une fonction à appeler sur l'objet avant de le libérer quand il n'est plus référencé.
                                  Donc, la classe *_ptr correspond à l'enveloppe du GC, le pointeur stocké dedans correspond à l'objet managé, et le destructeur à le même rôle dans les deux cas.

                                  • Après je me souviens de discussions sur fclc++ ou clc++m où un avantage était prêté aux GC : le MT car ils n'utilisaient pas de compteurs atomiques.

                                  C'est juste que tels qu'ils sont définis, l'implémentation des *_ptr rend difficile l'utilisation d'algorithme de GC autres que les algorithme on-time de type comptage de références.
                                  Le problème est que vu que tous les objets ne sont pas managé et les références ne peuvent être trouvées par exploration des objets, on ne peut pas vraiment faire autrement que d'intercepter la création et libération des références.

      • [^] # Re: templates variadiques

        Posté par . Évalué à 3.

        J'ai du mal a voir l’intérêt du scoped_ptr comme tu le présentes. Un objet classique (non crée avec un new) est lui aussi libéré quand on quitte le bloc non?

        Quant au shared_ptr c'est loin de pouvoir à lui tout seul dispenser d'un GC vu qu'il ne gèrera pas tout seul les référence circulaire. Si on lui fait aveuglément confiance, ca peut au contraire poser de nouveaux problèmes de leak.

        • [^] # Re: templates variadiques

          Posté par . Évalué à 2.

          Chez nous on l'utilise pour les pimpls (Pointer to IMPLementation) une techique très simple pour séparer l'interface de l'implémentation d'un classe.
          Ca évite de se soucier de faire le ménage. Un delete est si vite oublié...

          a++

        • [^] # Re: templates variadiques

          Posté par . Évalué à 4.

          L'intérêt c'est de pouvoir gérer aussi facilement un objet alloué sur le tas (avec new) qu'un objet alloué sur la pile (ce que tu appelles un objet classique).

          Un objet non-copiable est bien plus facile à manipuler (on peut le mettre dans un std::vector, par exemple) s'il est alloué sur le tas.

          Pour ce dernier point, il y a aussi la move semantics, tu me diras. Mais un autre intérêt des objets alloués sur le tas, c'est qu'ils gardent la même adresse tout le temps et qu'ils peuvent donc être référencés ailleurs. Entretenir un pointeur sur un objet alloué sur la pile, c'est casse-tête et error-prone, surtout si ledit objet subit des moves.

      • [^] # Re: templates variadiques

        Posté par (page perso) . Évalué à 5.

        Quel est l'interêt des pointeurs intelligents face à l'utilisation d'une variable sur la stack ?

        • [^] # Re: templates variadiques

          Posté par (page perso) . Évalué à 5.

          L'allocation dynamique peut-être ? Pour des données de tailles variable par exemple.

          • [^] # Re: templates variadiques

            Posté par . Évalué à 2.

            En C++ on ne s'amuse pas a faire de l'allocation dynamique à la main. std::vector<A> remplace aisément un char[A] (std::vector est forcement contigu, merci le standard).

            Par contre, cet exemple avec les scoped_ptr est plus parlant :

            std::scoped_ptr<A> a;
            try {
                // si je met un A a; ici, il dépassera pas le try.
                a.reset(new A(arguments));
            } catch (ZOmgException& e) {
                // oups
            }
            if (a->cavapas())
                a.reset(new A(arguments_qui_vont_mieux));
            
            
        • [^] # Re: templates variadiques

          Posté par . Évalué à 2.

          Pour les objets qui ne supportent pas le swap, on peut faire un swap de pointeur.

          class Foo {
              scoped_ptr<Bar> bar;
          
              void frobnicate() {
                  scoped_ptr<Bar> futurebar(...);
                  // insérer ici d'éventuelles exceptions
                  swap(futurebar, bar);
              }
          
              // Foo est incopiable
              // Bar n'est même pas swapable
          };
          
          
        • [^] # Re: templates variadiques

          Posté par (page perso) . Évalué à 3.

          scoped_ptr<MonObjet> ptr( factory->createMonObjet() );
          
          

          http://devnewton.bci.im

          • [^] # Re: templates variadiques

            Posté par . Évalué à 1.

            Et puis quoi encore ? Il se passe quoi si je fait un factory->createMonObjet() tout cours ? Ça fait des fuites ?

            Dans ce cas la, il faut que createMonObjet utilise std::unique_ptr (voire même le std::auto_ptr de notre bon vieux c++03). Après tu remplacera ton scoped_ptr par un unique_ptr (ou alors tu garde ton scoped_ptr mais tu release le unique_ptr).

            • [^] # Re: templates variadiques

              Posté par (page perso) . Évalué à 1.

              Et puis quoi encore ?

              Tout! Je donne juste un exemple d'utilisation du scoped_ptr... Ca sert juste si tu as un pointeur dont le cycle de vie est dans un "scope" ni plus, ni moins.

              http://devnewton.bci.im

              • [^] # Re: templates variadiques

                Posté par . Évalué à 2.

                Pour le scoped_ptr, j'ai rien à redire, mais pour le fait qu'une fonction renvoie un simple A* alloué sur le tas, j'appelle ça une future fuite mémoire ou un futur bug, il y a aura bien un moment ou toi ou quelqu'un d'autre oubliera que cette fonction alloue un objet, ou quelqu'un qui ira modifier ta fonction pour retourner des choses qui ne sont pas alloués.
                Moi-même j'ai tendance à supposer que tout les A* sont des pointeurs vers des choses qui ne t'appartiennent pas.

                Je vois pas à quoi ça sert d'avoir des pointeurs intelligents si c'est pour se retrouver avec le même genre de problèmes que lorsqu'on en avais pas.

                • [^] # Re: templates variadiques

                  Posté par (page perso) . Évalué à 1.

                  Moi-même j'ai tendance à supposer que tout les A* sont des pointeurs vers des choses qui ne t'appartiennent pas.

                  Il ne faut jamais supposer ça, car malheureusement il n'y a aucun moyen de distinguer le bon A* du mauvais A*. Si tu maitrises le code en question tu peux toujours faire autrement, mais si ça vient d'une bibliothèque externe...

                  http://devnewton.bci.im

              • [^] # Re: templates variadiques

                Posté par . Évalué à 1.

                Son intérêt est limité. C'est pour cela qu'il n'a pas rejoint le standard. std::unique_ptr<> remplit non seulement le rôle du boost::scope_ptr<>, mais aussi de std::auto_ptr<>. D'où le dialogue de sourds avec Batchyx je sens.

      • [^] # scoped_ptr

        Posté par . Évalué à 2.

        C'est unique_ptr<> qui a rejoint le standard, pas scoped_ptr<> qui ne peut pas être déplacé.

  • # z'avez oublié...

    Posté par . Évalué à -10.

    le distributeur de vodka.

  • # code typo

    Posté par . Évalué à 10.

    Ceci en fait delege pas le constructeur mais construit une instance temporaire de MyClass sans rien faire de plus (c'est du simple C++).

    class MyClass {
       public:
            MyClass(int num);
            MyClass() {MyClass(42)};
    }
    
    

    Voici la version qui delege le constructeur (et donc specifique au C++0x):

    class MyClass {
        public:
            MyClass(int num) {};
            MyClass(): MyClass(42) {};
    }
    
    
  • # Merci

    Posté par . Évalué à 6.

    Superbe dépêche, merci. (et oui, y a pas que patriiick_g :)
    (même moi j'ai compris, heu, en faisant un peu semblant parfois...)

    Qui complète à merveille, en proposant ce résumé à conserver précieusement, le hors-série de Linux Mag en ce moment dans tout les bons kiosques.

    gnulinuxmag

    • [^] # Re: Merci

      Posté par . Évalué à 2.

      La dépêche me laisse sur ma faim : c'est un inventaire assez complet et très bien rédigé des nouveautés du langage mais sans prise de recul, sans informations de contexte.

      On voit fleurir ici ou là des analyses sur les implications de ce nouveau standard; certains vont même jusqu'à supputer un retour en grâce du code natif face au code "managé". C'est ce genre d'informations qui me manque dans cette dépêche (et dans celles de patrick_g).

      BeOS le faisait il y a 15 ans !

      • [^] # Re: Merci

        Posté par . Évalué à 7.

        On voit fleurir ici ou là des analyses sur les implications de ce nouveau standard; [coupé] C'est ce genre d'informations qui me manque dans cette dépêche

        Bah, personnellement je vois ce genre d'analyse comme aller chez une voyante: une perte de temps.
        L'adaptation du standard dépendra de beaucoup de facteurs comme:
        1) le standard sera t'il implémenter par la plupart des compilateurs?
        2) les implémentations seront elles fiables ou buggées?
        3) les modes informatiques du moment

        Tout ces facteurs étant assez imprévisibles, une analyse revient à sortir la boule de cristal: bof!

  • # Quelque petite remarques

    Posté par . Évalué à 10.

    Merci pour la dépêche très intéressante.

    Quelques remarques:
    1) dans

    for (std::vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)
    sera remplacé par :
    for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)```
    
    

    il faut remplacer les .begin et .end par .cbegin et .cend, j'imagine pour que l'inférence de type produise un const_iterator au lieu d'un iterator, c'est logique mais comme cela nécessite de changer les habitudes des programmeurs, ça fera probablement une "erreur" (mineure) commune de plus.

    2) le nullptr:

    bool   b = nullptr;     // OK. b est faux.
    
    

    C'est plutôt choquant comme choix. J'imagine que c'est dans la continuité du C/C++ qui considère que 0(int) correspond à faux(booléen) mais bon on peut voir ça aussi comme persister dans l'erreur..

    3) unicode: utiliser le prefix u pour UTF-16, u8 pour UTF-8 me choque: de facto ça revient à légitimer UTF-16 comme le format par défaut, alors que son seul avantage est que c'est le format par défaut sous Windows, pour le reste c'est un format bâtard entre UTF-8(plus compact dans la plupart des cas, compatible ascendant avec ASCII) et UTF-32 (accès O(1) a tous les éléments d'une chaîne).

    • [^] # Re: Quelque petite remarques

      Posté par . Évalué à 2.

      UTF-16 (enfin, une variante) est aussi utilisé dans les String Java et dans la classe String de Qt.

      Moi je me demande pourquoi on ne passe pas (au moins dans les langages un peu plus haut niveau que C/C++) à UTF-32 pour les chaînes de caractères. Ça éliminerait plein d'erreurs liées au fait que le nombre d'octets pour coder un caractère n'est pas constant avec UTF-8 et UTF-16. Et les mémoires actuelles pourraient très bien le gérer, il n'y a pas non plus 50 milliards de chaînes de caractères dans un programme.

      • [^] # Re: Quelque petite remarques

        Posté par (page perso) . Évalué à 1.

        UTF-16 (enfin, une variante) est aussi utilisé dans les String Java et dans la classe String de Qt.
        Moi je me demande pourquoi on ne passe pas (au moins dans les langages un peu plus haut niveau que C/C++) à UTF-32 pour les chaînes de caractères. Ça éliminerait plein d'erreurs liées au fait que le nombre d'octets pour coder un caractère n'est pas constant avec UTF-8 et UTF-16. Et les mémoires actuelles pourraient très bien le gérer, il n'y a pas non plus 50 milliards de chaînes de caractères dans un programme.

        Je crois que Qt ne code que le plan0 d'unicode soit de 0000 à FFFF, donc un accès en O(1) aux caractères. Par contre la doc de Qt n'indique pas l'encodage utilisé pour le QChar.
        Un expert pour confirmer ?

    • [^] # Re: Quelque petite remarques

      Posté par (page perso) . Évalué à 10.

      J'imagine que c'est dans la continuité du C/C++ qui considère que 0(int) correspond à faux(booléen)

      J'imagine plutôt que c'est pour facilité les test de pointeur null. Par exemple, pouvoir écrire directement if(monptr) abc(); else throw monexception; plutôt que de voir écrire if(monptr==nullptr).

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

    • [^] # Re: Quelque petite remarques

      Posté par . Évalué à 1.

      il faut remplacer les .begin et .end par .cbegin et .cend, j'imagine pour que l'inférence de type produise un const_iterator au lieu d'un iterator, c'est logique mais comme cela nécessite de changer les habitudes des programmeurs, ça fera probablement une "erreur" (mineure) commune de plus.

      Et on a maintenant deux fonctions .begin et .cbegin qui retournent un const_iterator (pareil pour end), pour ne pas casser le code qui utilisait .begin sur un vector const ... c'est moche.

      • [^] # Re: Quelque petite remarques

        Posté par . Évalué à 9.

        c'est moche.

        Bin c'est du C++, tu t'attendais à quoi?

      • [^] # Re: Quelque petite remarques

        Posté par (page perso) . Évalué à 5.

        Ouais mais bon le but c'est justement de simplifier la syntaxe. Avec l'inférence de type ici, tu t'affranchis des lourdeurs d'ecriture du genre std::conteneur<Type>::const_iterator nom_de_literator. Après, il faut juste que ça rentre dans les mœurs du programmeur C++

  • # Bof bof bof

    Posté par (page perso) . Évalué à 4.

    Déjà que le C++ était un langage relativement complexe, dont très peu d'humains sur terre maîtrisent toutes les subtilités (qui comprends réellement les appels de destructeurs virtuels des objets dans le cas d'une exception levée dans le constructeur d'un objet ?), là on entre dans une nouvelle ère.

    Et que fait-on ? On rajout des mot-clés, avec des nouveaux comportements tout aussi subtils que les précédents, mais attention, qui peuvent aussi s'y cumuler. Les syntaxes des nouveaux concepts (lambda, foreach) ne ressemble pas du tout à la syntaxe typique du C++ et ça fait grosse verrue.

    Parfois, il faut accepter qu'un langage est fini et que toute modification du langage ne va pas forcément dans le sens de son amélioration.

    A mon sens, quitte à faire un nouveau langage avec des super fonctionnalités, mieux vaut faire ça dans un autre langage, avec une syntaxe flexible et travailler à mort sur l'interaction entre le C++ et ce nouveau langage.

    Par contre, quelques évolutions de la STL, pourquoi pas.

    • [^] # Re: Bof bof bof

      Posté par . Évalué à -1.

      Tout à fait d'accord, pourquoi vouloir complexifier un langage qui se devait être proche du système ?

      Travaillant dans l'embarqué, je peux dire que le C++ est utilisé de la façon la plus simple possible sans templates et fioriture de ce type qui embrument la lecture et la reprise de code.

      Quel drôle d'idée aussi de vouloir supprimer le typage des données avec "auto"?

      Laissons le C et le C++ là où ils sont et utilisons les langages dans les domaines pour lesquels ils sont fait.

      • [^] # Re: Bof bof bof

        Posté par (page perso) . Évalué à 10.

        Tant que plus d'abstraction et d'expressivité ne se font pas au détriment de l'efficacité, je ne vois pas pourquoi on s'en passerait.

        Quel drôle d'idée aussi de vouloir supprimer le typage des données avec "auto"?

        auto ne supprime pas le typage des variables, il permet juste de ne pas avoir à ré-écrire le type lorsque celui-ci est implicitement fourni par la valeur initiale qu'on souhaite affecter à la variable.
        L'exemple de la dépêche est -je trouve- significatif :

        for (std::vector::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)
        sera remplacé par :
        for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

        Tous ceux en ont marre de passer leur temps à devoir se taper des kilomètres de préfixes templatés dans leurs boucles for comprendront.
        Alors on peut reprocher le manque de lisibilité peut-être : "c'est quoi itr exactement ?", mais je pense que c'est une question d'habitude.

        • [^] # Re: Bof bof bof

          Posté par (page perso) . Évalué à -3.

          Tous ceux en ont marre de passer leur temps à devoir se taper des kilomètres de préfixes templatés dans leurs boucles for comprendront.

          Bah utilises un autre langage... plus moderne..

          « Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker

          • [^] # Re: Bof bof bof

            Posté par (page perso) . Évalué à 3.

            Comme si on avait toujours le choix :)

          • [^] # Re: Bof bof bof

            Posté par . Évalué à 10.

            Un langage moderne comme le tout nouveau C++ ?

            BeOS le faisait il y a 15 ans !

            • [^] # Re: Bof bof bof

              Posté par (page perso) . Évalué à 3.

              En l'occurence, l'inférence de type a l'air de pallier à un autre défaut du C++, qui est la verbosité extrème de l'utilisation des template. Il aurait mieux valu addresser le problème à la source, avant de faire une syntaxe à rallonge, non ?

              Et je parle pas des erreurs de gcc quand tu as une erreur de compile sur des string !

              • [^] # Re: Bof bof bof

                Posté par . Évalué à 3.

                Et je parle pas des erreurs de gcc quand tu as une erreur de compile sur des string !

                Ca c'est quand meme un probleme bien specifique a GCC, il y a plein d'autres compilateurs qui s'en sortent beaucoup mieux et qui ne te sortent pas de la bouillie a chaque erreur sur des templates (meme si ca arrive aussi dans les cas plus compliques bien evidemment)

                • [^] # Re: Bof bof bof

                  Posté par . Évalué à 0.

                  Sans parler de l'indispensable STLfilt. Les noms de types à rallonge ne sont pas un vrai problème avec les bons outils.

            • [^] # Re: Bof bof bof

              Posté par . Évalué à 0.

              A peu près aussi moderne qu'une nouvelle version de bash :)

      • [^] # Re: Bof bof bof

        Posté par . Évalué à 10.

        Quel drôle d'idée aussi de vouloir supprimer le typage des données avec "auto"?

        Ce n'est absolument pas une suppression de typage, c'est du sucre syntaxique bien venu pour éviter d'avoir une déclaration à rallonge. Si tu codes un peux avec des nested class, et autre type déclaré dans une classe particulière, et qu'au final tu te retrouve avec 4 ou 5 :: selon le namespace sans oublier des paramètre template au passage, rien que la première ligne de ton for dépasse allègrement les 80 caractère et n'ajoute aucune lisibilité du code; au contraire, les mot clé est perdu au milieu d'un babillage inutile.
        Ta variable elle aura toujours un type qui sera déterminé à la compilation.

        sans templates et fioriture de ce type qui embrument la lecture et la reprise de code

        donc pas de std::string? pas de std::vector ? pas de std::map? std::set? pour ne citer que les plus fréquents ?

        le C++ ? un langage qui se devait être proche du système ?
        Tu confonds avec le C; le C++ était justement l'éloignement du coté bas niveau tout en conservant la syntaxe du C

        Laissons le C et le C++ là où ils sont et utilisons les langages dans les domaines pour lesquels ils sont fait.

        Pourquoi devrait il être figé? Je continue à coder des trucs en c++, même des nouveaux projets, et de temps en temps il y a des manques; comme le foreach. Ça tombe bien boost à le sien, et Qt aussi

        Il ne faut pas décorner les boeufs avant d'avoir semé le vent

        • [^] # Re: Bof bof bof

          Posté par (page perso) . Évalué à 0.

          Quitte à faire du sucre syntaxique, pourquoi ne pas coder dans un langage propre qui génère du C++ crade. Comme ça, au moins, on peut avoir d'autres fioritures.

          Sinon, les std::trucs, c'est pas toujours la panacée. Dés que tu veux faire des structures imbriquées, bonjour le bazar. Alors qu'en Python (ou en lisp), on va souvent s'abstenir de définir une classe parce que un dictionnaire de tuple de liste de string, ca s'écrit tellement vite que t'y penses pas. Et du coup, tu peux faire des manipulations faciles sur des arbres de données avec tous les avantages de la programmation orientée Data. En C++, tu te retrouves vite à trainer plusieurs classes, qui sont lourdes à faire évoluer, sur lesquelles tu vas rajouter une ou deux méthodes, etc.

          • [^] # Re: Bof bof bof

            Posté par . Évalué à 7.

            Alors qu'en Python (ou en lisp), on va souvent s'abstenir de définir une classe parce que un dictionnaire de tuple de liste de string, ca s'écrit tellement vite que t'y penses pas. Et du coup, tu peux faire des manipulations faciles sur des arbres de données avec tous les avantages de la programmation orientée Data.

            Et tu te retrouve avec des structures qui n'ont plus aucune sémantiques (en plus avec un langage à typage dynamique on augmente encore le coté flou du code). On se tappe des doc à rallonge pour expliquer que tel numéro de champ a tel ou tel signification et tel type. On ne vérifie que dans les tests fonctionnels (s'ils sont fait et de toute manière ils ne seront pas complets) la bonne utilisation de ses structure. Là où utiliser une classe rend le code lisible, clair et testable (on peut ajouter des assertions à chaque accès à une donnée pour vérifier si elle est utilisée comme il se doit).

            En plus de ça tu as un couplage fort entre l'utilisation des données et leur représentation. C'est un peu aténué par le ducktyping, mais ça ne me semble pas suffisant.

            Pour moi se passer de classe et tout balancer dans des dictionnaires c'est pas de l'orienté objet (ça se défend, il y a des langages qui font ça très bien comme ADA (même si maintenant ils ont ajouté de l'objet dans le langage)).

            Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

          • [^] # Re: Bof bof bof

            Posté par . Évalué à 2.

            on va souvent s'abstenir de définir une classe parce que un dictionnaire de tuple de liste de string, ca s'écrit tellement vite que t'y penses pas.

            Marrant ça fait aussi partie des addition du langage, avec en prime une vérification des types à la compile !!!

            Il ne faut pas décorner les boeufs avant d'avoir semé le vent

  • # commentaires

    Posté par . Évalué à 6.

    Bonne news dans l'ensemble, j'en profite pour rajouter mon grain de sel :o)

    La délégation de constructeur

    On peut également signaler un constructeur par défaut et même demander au compilateur de ne pas générer certaines fonctions membres (très pratique, pour interdire la copie par exemple)

    
    

    > On peut quand même regretter l'absence de sémaphore dans la spécification.

    À l'origine, boost::thread (dont s'inspire cette spécification) proposait un type semaphore mais il a été retiré parce qu'il était source d'erreurs (pas de notion de lock, inversion de priorité). La solution recommandée c'est d'utiliser un mutex + une condition, même si dans certains cas, ça ne convient pas (gestion de signaux posix entre autres).

    Derrière, std::thread propose la plupart des facilités des threads Posix (mutex, condition, barrière, R/W locks, etc...) en plus sûr mais également les futures. D'ailleurs, le langage gagne le mot clé thread_local pour signaler les variables TLS.

    Les expressions régulières font leur entrée dans la bibliothèques standard

    Je tiens à souligner que l'implémentation de référence (aka boost::regex de John Maddock) est particulièrement performante (et bénéficie énormément des templates), et offre bon nombre des facilités des expressions régulières compatibles Perl, ainsi qu'un header de compatibilité avec l'API Posix.

    Je trouve dommage qu'on ne mentionne pas la grosse nouveauté (depuis l'abandon des concepts pour ce round) de la norme mais également la plus complexe à appréhender: la sémantique move qui permet d'économiser certains copies inutiles mais impossible à éviter de par le langage. Les utilisateurs peuvent ainsi bénéficier de façon transparente de cette optimisation à travers la bibliothèque standard si celle-ci a été adaptée en conséquence (mais leur code peut également en bénéficier à condition d'écrire un constructeur et un opérateur move adéquats)
    Bjarne stroustrup l'expliquera certainement beaucoup mieux que moi:
    http://www2.research.att.com/~bs/C++0xFAQ.html#rval
    Certes, ça complexifie le langage mais c'est un exemple typique de ce qui fait l'attrait du C++: un langage extrêmement souple, performant et robuste.

    • [^] # Re: commentaires

      Posté par . Évalué à 0.

      C++ un langage performant et souple, ok, mais robuste??
      On parle quand même d'un langage où:
      1) les compilateurs ont historiquement eu beaucoup de bugs à cause de la difficulté d'implémentation du langage
      2) les applications ont pas mal de failles de sécurités

      Ta définition de robuste doit être très différente de la mienne..

      • [^] # Re: commentaires

        Posté par (page perso) . Évalué à 6.

        les applications ont pas mal de failles de sécurités

        C'est pas forcément dû au langage ça.

      • [^] # Re: commentaires

        Posté par . Évalué à 4.

        Que le compilateur soit difficile à mettre au point n'implique pas que le langage n'est pas robuste, il indique juste qu'il est complexe à implémenter.

        • [^] # Re: commentaires

          Posté par . Évalué à 2.

          Ça veut dire qu'il a moins de chances d'être facilement portable d'une implémentation à l'autre (c'est pareil avec les protocoles pourris comme SIP).

          • [^] # Re: commentaires

            Posté par . Évalué à 4.

            Il y a quelque années, mes profs me disaient : le C a été conçu pour simplifier la vie de ceux qui écrivaient des compilateurs C et le C++ pour simplifier la vie de ceux qui s'en servaient.

            • [^] # Re: commentaires

              Posté par (page perso) . Évalué à 4.

              Alors cela semble raté pour les 2.

              Quand on veut simplifier l'écriture d'un compilo, on rend au moins la grammaire context-free, ce qui n'est pas le cas de la grammaire C.

              
              

              Est-ce que je fais un « x fois y » ou la déclaration d'un pointeur y vers un objet de type x ? J'ai besoin du contexte pour le savoir.

              Pour ce qui est du C++, le langage est très très complexe. Il n'y a que dans les ministères (Je suis fonctionnaire. Oui, je sais : « condoléances ! ») qu'on croit qu'ajouter et ajouter et ajouter simplifie.

              • [^] # Re: commentaires

                Posté par (page perso) . Évalué à 2.

                mon

                x * y;

                a été mangé.

              • [^] # Re: commentaires

                Posté par . Évalué à 2.

                marrant ça j'aurais juré que l'assembleur, qui finalement ne fait qu'ajouter un peut de structure à tout ces 0 et 1 simplifiait grandement l'écriture du code.

                Oui ajouter des verrue à des lois ou a un langage de programmation n'est pas toujours heureux.

                Mais de ce que je vois, des évolutions c'est
                * de la simplification d'écriture ( auto, tuples ), avec autant de sécurité qu'avant.
                * des ajouts bienvenus ( la possibilité de dire au complilo qu'il ne doit pas créer de constructeurs implicite comme la copie par exemple.)

                Quand on veut simplifier l'écriture d'un compilo, on rend au moins la grammaire context-free, ce qui n'est pas le cas de la grammaire C.

                Je ne connais aucun langage de programmation, mise à part l'assembleur, qui soit context-free. Ne serait-ce que pour la gestion des chaines de caractères... Mais même sans prendre en compte le coup des chaine de caractère, la majorité des langage où tu peux définir un type (comme le java par exemple) ne sont pas context-free.

                Il ne faut pas décorner les boeufs avant d'avoir semé le vent

                • [^] # Re: commentaires

                  Posté par (page perso) . Évalué à 2.

                  Je ne connais aucun langage de programmation, mise à part l'assembleur, qui soit
                  context-free.

                  De mémoire :
                  Pascal, ada, modula, lisp (La plupart des dialects, pour ce qui est de l'ensemble des primitives, le reste est implémenté à partir de ces primitives), haskell, ...

                  • [^] # Re: commentaires

                    Posté par (page perso) . Évalué à 3.

                    J'ai réessayé de comprendre ton commentaire et je ne le comprends toujours pas.

                    Les langages d'assemblages n'ont rien ajoutés, ils ont supprimés des choses en changeant de paradigme. En codant direct en binaire, le programmeur faisait lui-même le compilateur. Les programmeurs de l'époque écrivait sur papier dans un langage abstrait et effectuait eux-même la traduction en binaire. Les langages d'assemblage ont (principalement) supprimé cette traduction manuelle.

                    Les langages de second niveau ont supprimé une phase de traduction supplémentaire. Les programmeurs avaient tendance à ne pas penser en terme d'archi particulière (registres, jeux d'instruction, ...) mais en terme d'opération, construction et effectuait la traduction en manuel vers la machine. On a donc supprimé cette phase.

                    Si on prend des langages OO comme Smalltalk, on remarque immédiatement la simplification sémantique et syntaxique mais une puissance formidable. De même pour lisp qui peut se résumer en 20 primitives (Un interpréteur jouet tient sur 50 lignes de code).

                    Pour moi, la simplification va vers la voie de l'unification pas de la dispersion. Bien sur qu'il est intéressant d'avoir du sucre syntaxique et des facilités de ce genre. Mais, il faut que ce sucre syntaxique reste du sucre syntaxique, c'est à dire soit juste de la transformation de cette syntaxe vers un noyau solide et cohérent. Le C++ n'a pas de noyau solide, simple et cohérent. Le C++ c'est du C avec des class, des templates et des milliers d'autres bricolages.

                    Pour ce qui est de ta remarque sur le traitement des strings, je ne la comprends pas du tout. Je n'arrive pas à imaginer un seul langage avec des littéraux de chaine non context-free. C'est trivial dans une grammaire :

                    lit-string -> \" chars \" | \""" chars-and-cr \"""

                  • [^] # Re: commentaires

                    Posté par . Évalué à 3.

                    Je pense qu'il y a confusion entre la grammaire du langage (qui peut ou pas être hors contexte) et le langage lui même : pour définir la sémantique d'un programme et donc le compiler tu as besoin de connaître le type d'une variable, et c'est complètement dépendant du contexte (le nom d'une variable seul ne suffit pas pour avoir son type)

                    • [^] # Re: commentaires

                      Posté par (page perso) . Évalué à 1.

                      J'ai pourtant été clair. J'ai parlé de la grammaire du C, pas du langage C.

                    • [^] # Re: commentaires

                      Posté par . Évalué à -2.

                      ah ? je n'ai jamais entendu parler de cette différence entre langage context-free et grammaire context-free, aurais-tu des liens ?

                      • [^] # Re: commentaires

                        Posté par . Évalué à -2.

                        je cite Wikipedia
                        The languages generated by context-free grammars are known as the context-free languages.

                        Je pense que ton idée de "langage context-free" n'est ni techniquement (contraintes de mémoire et de cpu) ni même intellectuellement viable (imagine que tu veuilles interdire la définition d'une même variable plusieurs fois avec la même portée, c'est de la folie pure et simple).

                        • [^] # Re: commentaires

                          Posté par (page perso) . Évalué à 2.

                          La grammaire du C est context-free. On est d'accord jusque là ? Le langage défini par la grammaire du C accepte des suites de caractère un programme grammaticalement correct. Ce programme peut encore être invalide pour d'autres raisons que des raisons grammaticales. Par analogie, par exemple, la phrase « La vache salée avec des plumes pianote un ours bleu » est grammaticalement correcte mais n'a pas de sens. On ne peut pas vraiment considérer cette phrase comme du français.

                          Par contre, le langage C est turing complete.

                          • [^] # Re: commentaires

                            Posté par (page perso) . Évalué à 3.

                            Vu que la grammaire C n'est pas context-free, remplacer C par pascal... Mais à 4h du mat j'ai des problèmes pour penser :/

                • [^] # Re: commentaires

                  Posté par . Évalué à 1.

                  marrant ça j'aurais juré que l'assembleur, qui finalement ne fait qu'ajouter un peut de structure à tout ces 0 et 1 simplifiait grandement l'écriture du code.

                  Oui ajouter des verrue à des lois ou a un langage de programmation n'est pas toujours heureux.

                  Je crois que tu n'as pas compris la remarque concernant la syntaxe de C++. Il est tout à fait possible de faire un langage puissant avec une grammaire simple (cf. Python).

                • [^] # Re: commentaires

                  Posté par . Évalué à -2.

                  Je ne connais aucun langage de programmation, mise à part l'assembleur, qui soit context-free.

                  jump label;
                      ...
                  label:
                      ; context-free ?
                  
                  
              • [^] # Re: commentaires

                Posté par . Évalué à -3.

                Est-ce que je fais un « x fois y » ou la déclaration d'un pointeur y vers un objet de type x ? J'ai besoin du contexte pour le savoir.

                bah le problème est résolu par l'analyse lexicale, certes il y a un contexte mais pas celui de la grammaire.

                • [^] # Re: commentaires

                  Posté par . Évalué à 4.

                  Toi, t'as pas dû (encore) faire de projet compil pour sortir des énormités pareilles...

                  • [^] # Re: commentaires

                    Posté par . Évalué à -1.

                    Un exemple avec lex
                    Un autre avec antlr

                    Je dirais plutôt que "L'un de nous deux n'a pas dû (encore) faire de projet compil" a plus de chance d'être vrai...

                    • [^] # Re: commentaires

                      Posté par . Évalué à 3.

                      Je reprends ce que tu disais :

                      Est-ce que je fais un « x fois y » ou la déclaration d'un pointeur y vers un objet de type x ? J'ai besoin du contexte pour le savoir.

                      bah le problème est résolu par l'analyse lexicale, certes il y a un contexte mais pas celui de la grammaire.

                      Le problème ne peut pas être résolu par l'analyse lexicale, ce n'est pas possible. Le résultat de l'analyse lexicale pour le morceau de code incriminé c'est quelquechose du genre : «identifiant x, symbole *, identifiant y». Et ça ne dit absolument rien sur le sens de ce morceau, qui doit être déterminé par l'analyse sémantique (comme son nom l'indique). Donc, je pense vraiment que tu ne comprends rien à ce que tu dis. Ou alors, dis moi comment tu fais pour résoudre le problème.

                      • [^] # Re: commentaires

                        Posté par . Évalué à -3.

                        Si tu souhaites analyser "x * y;" et que ton lexer retourne identifiant quelque soit le type de x et de y alors ta grammaire est ambigüe et par conséquent impossible à analyser (en tout cas avec l'approche utilisée par tout langage de programmation). Quand tu lis une définition de x tu définis son type. Quand tu lis x tu retournes son type. Une petite pile pour gérer le tout et le tour est joué. C'est le sens du lexème qui joue pas celui de l'arbre syntaxique.

                        • [^] # Re: commentaires

                          Posté par . Évalué à 2.

                          Il y a des langages où "x * y" est non-ambigu, merci.

                          • [^] # Re: commentaires

                            Posté par . Évalué à -2.

                            il y a des langages amibus ?

                            • [^] # Re: commentaires

                              Posté par . Évalué à -2.

                              ambigus, pardon, posté trop vite

                            • [^] # Re: commentaires

                              Posté par . Évalué à 2.

                              Je voulais dire qu'il y a des langages où cela est non-ambigu dès l'analyse grammaticale locale, sans avoir besoin du moindre élément de contexte environnant (i.e. x * y est toujours une invocation de l'opérateur multiplicatif, et jamais autre chose).

                              (je trouve amusant que tu nous infliges une bouillie verbale indigeste à chaque message, et que tu sois incapable de comprendre une réponse simple à tes arguments)

                              • [^] # Re: commentaires

                                Posté par . Évalué à -3.

                                mais si ! mais si ! je comprends très bien vos arguments ! bon, peut-être certains ici penseront que c'est faire preuve d'intelligence que de dire que la grammaire du C si elle était context-free elle serait ambigüe donc context-sensitive on s'en sort que comme ça et c'est moche, moi non ! je propose juste une alternative car je pense qu'on passe à côté d'une chose essentielle : la base de toute grammaire c'est le lexème, l'unité de sens, le caractère n'en est pas une. je propose juste une approche qui me semble plus élégante et plus juste : pas d'ambigüité, context-free et surtout plus proche de la sémantique que tout le monde verra dans le programme. ya rien d'impossible dans cette démarche (la seule possible d'ailleurs ? l'autre est réalisable ?). c'est l'approche qui me semble la bonne. une grammaire reste un outil, à chacun de savoir l'utiliser correctement. voilà mon point de vue, c'est plus clair ?

                                • [^] # Re: commentaires

                                  Posté par . Évalué à 4.

                                  Non mais tu réinventes la roue carrée là, je t'assure. Ce que tu décris, c'est ce que fait le compilateur, sauf qu'il le fait au bon moment.

                                  Si je comprends ce que tu veux dire en le reformulant, c'est : au moment de l'analyse syntaxique, quand on lit «identificateur x», on peut savoir que x est un type (avec une pile dis-tu) et donc, c'est non-ambigu. Alors, je te rassure, c'est ce qu'un compilateur fait, sauf qu'il le fait dans la phase suivante, l'analyse sémantique. Parce que déjà, pour savoir que x est un type (défini précédemment), il faut avoir définit sa sémantique. Ensuite, ta pile, en fait, ça s'appelle une table des symboles et ça s'implémente mieux avec une hashmap ou un arbre, et ça se construit au moment de l'analyse sémantique. Et la grammaire reste ambiguë, c'est pas parce qu'on sait résoudre les ambiguïtés que la grammaire devient non-ambiguë sinon, on ne pourrait rien compiler. L'analyse syntaxique, elle se contente de renvoyer une suite de léxème, qui sont en entrée de l'analyse sémantique.

                                  • [^] # Re: commentaires

                                    Posté par . Évalué à -5.

                                    En gros il n'y a pas d'ambiguïté, elle est morte dans l'oeuf, on n'a même pas appliqué la moindre règle que l'ambiguïté est résolue. Pourquoi parler de grammaire ambigüe ? Pourquoi parler de non context-free ? Pas besoin de backtracking sur l'analyse syntaxique, pas de multithreading, etc. On n'est pas à la limite du ridicule là ?

                                    • [^] # Re: commentaires

                                      Posté par (page perso) . Évalué à 4.

                                      Non, on est n'est pas à la limite du ridicule : tu es ridicule !

                                      La grammaire du C n'est pas context-free parce qu'il n'y a pas moyen de la parser avec un pushdown automaton POINT BARRE.

                                      • [^] # Re: commentaires

                                        Posté par . Évalué à 1.

                                        avec un pushdown automaton

                                        Dont il semble que quelques irréductibles Québécois le nomment «automate poussant vers le bas».

      • [^] # Re: commentaires

        Posté par . Évalué à 3.

        Ta définition de robuste doit être très différente de la mienne..

        D'ailleurs, saikoi être "robuste" pour un langage ? Une implémentation (ie un compilo, un runtime, whatever), ok, mais un langage ?

        • [^] # Re: commentaires

          Posté par . Évalué à 3.

          C'est plus ou moins la question que je posais à GeneralZod..
          Pour moi je peux voir 2 choses qui correspondraient plus ou moins:
          - un langage bien défini sémantiquement, je crois que SML est dans ce cas.
          - un langage dont les opérations sont plus "robuste" par ex: si tu as un débordement entier en Ada tu as une exception, en Python il passe au bigInt/bignum (j'ignore ce qui se passe si le bigInt déborde??) contrairement au C/C++ ou tu as une erreur silencieuse.

      • [^] # Re: commentaires

        Posté par (page perso) . Évalué à 8.

        Concernant les failles de sécurité, si on code comme en C++ (et pas comme en C), on évite pas mal de problèmes. C'est à dire :
        - string au lieu de char*
        - vector au lieu de T*
        - scoped_ptr au lieu de T*
        - const T& au lieu de T*
        - etc...

  • # Coquille

    Posté par (page perso) . Évalué à 2.

    J'arrive un peu tard, mais si l'un des Puissants de DLFP passe par là il pourra corriger ça :
    « Les listes d'initialisations permettent d'initialiser les objets en passant une liste de variable entre crochets » –> entre accolades.

  • # La route est longue mais la voie est bonne...

    Posté par . Évalué à 1.

    J'ai quelques heures d'avance, mais je me lance:

    • inférence de type
    • lambda

    Il reste du chemin, mais le C++ prend le bon chemin, qui le mènera vers... n'importe quel langage fonctionnel moderne!

    Bon Vendredi!

    • [^] # Re: La route est longue mais la voie est bonne...

      Posté par . Évalué à 1.

      Et ?
      Aucun des langages mainstreams qui joue dans la même cours que lui (relativement aux d'applications types) n'est fonctionnel. Est-ce parce qu'il existe des langages dédiés qu'il ne devrait pas offrir des éléments d'un nouveau paradigme ?
      Ca serait idiot.

    • [^] # Re: La route est longue mais la voie est bonne...

      Posté par . Évalué à -2.

      Il existe des langages fonctionnels qui font de la metapropgrammation ?

      Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.