Question

Pouvez-vous les développeurs C ++ s'il vous plaît nous donner une bonne description de ce que RAII est, pourquoi il est important, et si oui ou non il peut avoir une pertinence par rapport aux autres langues?

faire en savoir un peu. Je crois qu'il représente « l'acquisition de ressources est Initialisation ». Cependant, ce nom ne jive avec mon (peut-être incorrecte) la compréhension de ce RAII est: Je l'impression que RAII est un moyen d'initialisation des objets sur la pile de telle sorte que, lorsque ces variables sont hors de portée, les Destructeurs sera automatiquement être appelé cause des ressources à être nettoyés.

Alors, pourquoi est-ce pas appelé « à l'aide de la pile pour déclencher le nettoyage » (UTSTTC :)? Comment obtenez-vous de là à « RAII »?

Et comment pouvez-vous faire quelque chose sur la pile qui provoque le nettoyage de quelque chose qui vit sur le tas? En outre, il y a des cas où vous ne pouvez pas utiliser RAII? Avez-vous jamais vous vous trouvez qui souhaite pour la collecte des ordures? Au moins un collecteur de déchets que vous pouvez utiliser pour certains objets tout en laissant les autres gérer?

Merci.

Était-ce utile?

La solution

  

Alors, pourquoi est-ce pas appelé « à l'aide de la pile pour déclencher le nettoyage » (UTSTTC:)?

RAII vous dit ce qu'il faut faire: Acquire votre ressource dans un constructeur! Je voudrais ajouter: une ressource, un constructeur. UTSTTC est juste une application de cela, RAII est beaucoup plus.

Gestion des ressources suce. Ici, ressource est tout ce qui a besoin d'un nettoyage après utilisation. Des études de projets à travers de nombreuses plates-formes montrent que la majorité des bogues sont liés à la gestion des ressources - et il est particulièrement mauvaise sur Windows (en raison des nombreux types d'objets et de l'affectation)

.

En C ++, la gestion des ressources est particulièrement compliquée en raison de la combinaison des exceptions et des modèles (style C ++). Pour un coup d'oeil sous le capot, voir GOTW8 ).


C ++ garantit que le destructor est appelé si et seulement si le constructeur a réussi. En se fondant sur cette question, RAII peut résoudre de nombreux problèmes désagréables du programmeur moyen pourrait même ne pas être au courant. Voici quelques exemples au-delà de la « seront détruites mes variables locales chaque fois que je reviens ».

Commençons par une classe de FileHandle trop simpliste employant RAII:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

Si la construction échoue (à une exception), aucune autre fonction membre - pas même le destructor - est appelée.

RAII évite d'utiliser des objets dans un état non valide. il est déjà plus facile la vie avant d'utiliser même l'objet.

Maintenant, laissez-nous jeter un oeil à des objets temporaires:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

Il y a trois cas d'erreur à traités: aucun fichier peut être ouvert, un seul fichier peut être ouvert, les deux fichiers peuvent être ouverts, mais la copie des fichiers a échoué. Dans une mise en œuvre non RAII, Foo devrait traiter tous les trois cas explicitement.

RAII de libérer des ressources qui ont été acquises, même lorsque plusieurs ressources sont acquises dans une déclaration.

Maintenant, laissez-nous regroupons des objets:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

Le constructeur de Logger échouera si le constructeur de original échoue (car filename1 n'a pas pu être ouvert), le constructeur de duplex échoue (car filename2 n'a pas pu être ouvert), ou de l'écriture aux fichiers à l'intérieur du corps du constructeur de Logger échoue. Dans tous ces cas, le destructeur de Logger sera pas être appelé - nous ne pouvons pas compter sur destructor de Logger pour libérer les fichiers. Mais si original a été construit, son destructor sera appelé lors du nettoyage du constructeur de Logger.

RAII simplifie le nettoyage après la construction partielle.


Points négatifs:

Les points négatifs? Tous les problèmes peuvent être résolus avec RAII et pointeurs intelligents ;-)

RAII est parfois difficile à manier quand vous avez besoin d'acquisition différée, de pousser des objets agrégés sur le tas.
Imaginez l'enregistreur a besoin d'un SetTargetFile(const char* target). Dans ce cas, la poignée, qui doit encore être membre de Logger, doit résider sur le tas (par exemple dans un pointeur intelligent, pour déclencher de manière appropriée la destruction de la poignée.)

Je ne l'ai jamais souhaité pour la collecte des ordures vraiment. Quand je fais C # Je me sens parfois un moment de bonheur que je ne pas besoin de soins, mais beaucoup plus me manque tous les jouets frais qui peuvent être créés par la destruction déterministe. (À l'aide IDisposable ne vient pas le couper.)

J'ai eu une structure particulièrement complexe qui aurait pu bénéficier de GC, où des pointeurs intelligents « simples » provoqueraient des références circulaires sur plusieurs classes. Nous embrouillé par en équilibrant avec soin des pointeurs forts et faibles, mais chaque fois que nous voulons changer quelque chose, nous devons étudier un grand tableau de la relation. GC aurait pu être mieux, mais quelques-unes des ressources détenues composants qui devraient être libèrentDès que possible.


Une note sur l'échantillon FileHandle: Il n'a pas été conçu pour être complète, juste un échantillon - mais avéré incorrect. Merci Johannes Schaub pour remarquer et FredOverflow pour la transformer en une solution C ++ 0x correcte. Au fil du temps, je me suis installé avec l'approche documentée ici .

Autres conseils

Il y a d'excellentes réponses là-bas, donc je viens d'ajouter des choses oubliées.

0. RAII est sur les étendues

RAII est à la fois:

  1. l'acquisition d'une ressource (peu importe quelle ressource) dans le constructeur, et non l'acquisition dans le destructor.
  2. ayant le constructeur exécuté lorsque la variable est déclarée, et le destructor exécuté automatiquement lorsque la variable est hors de portée.

D'autres ont déjà répondu à ce sujet, je ne vais pas donner des détails.

1. Lors du codage en Java ou C #, vous utilisez déjà RAII ...

  

MONSIEUR JOURDAIN: Quoi! Quand je dis: « Nicole, apportez-moi mes pantoufles,   et me donner mon bonnet de nuit, »c'est la prose?

     

Maître de philosophie. Oui, Monsieur

     

MONSIEUR JOURDAIN: Depuis plus de quarante ans que je parle en prose sans rien savoir à ce sujet, et je suis bien obligé de me avoir appris que

.      

- Molière: Le Gentleman classe moyenne, Acte 2, Scène 4

Comme Monsieur Jourdain faisait de la prose, C # et même les gens Java utilisent déjà RAII, mais de manière cachées. Par exemple, le code Java suivant (qui est écrit de la même manière en C # en remplaçant synchronized avec lock):

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... utilise déjà RAII. L'acquisition de mutex se fait dans le mot-clé (ou de synchronized lock), et la non-acquisition se fera lors de la sortie du champ

Il est si naturel dans sa notation, il ne nécessite presque pas d'explication même pour les gens qui ont jamais entendu parler RAII.

L'avantage C ++ a plus de Java et C # est que tout peut être fait en utilisant RAII. Par exemple, il n'y a pas d'accumulation en directe équivalent de synchronized ni lock en C ++, mais nous pouvons toujours les avoir.

En C ++, il serait écrit:

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

qui peut être facilement écrit comme Java / C # (en utilisant les macros de C):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. RAII ont d'autres utilisations

  

WHITE RABBIT: [chant] Je suis en retard / je suis en retard / Pour une date très importante. / Pas le temps de dire « Bonjour. » / Au revoir. / Je suis en retard, je suis en retard, je suis en retard.

     

- Alice au pays des merveilles (version Disney, 1951)

Vous savez quand le constructeur sera appelé (à la déclaration d'objet), et vous savez que lorsque son destructor correspondant sera appelé (à la sortie du champ d'application), de sorte que vous pouvez écrire un code presque magique avec mais une ligne. Bienvenue sur le C ++ wonderland (au moins, à partir d'un point de vue C ++ de développeur).

Par exemple, vous pouvez écrire un objet compteur (je laisse cela comme un exercice) et l'utiliser tout en déclarant sa variable, comme l'objet de verrouillage ci-dessus a été utilisé:

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

qui, bien sûr, peut être écrit, encore une fois, la façon dont Java / C # en utilisant une macro:

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. Pourquoi ne C ++ manque finally?

  

[CRIER] Il est finale compte à rebours!

     

- Europe: The Final Countdown (désolé, je suis sorti de citations, ici ...: -)

La clause finally est utilisé en C # / Java pour gérer l'élimination des ressources en cas de sortie de périmètre (soit par une return ou levée d'une exception).

lecteurs de spécification auront remarqué Astute C ++ n'a pas finally. Et ce n'est pas une erreur, parce que C ++ n'a pas besoin, comme déjà RAII gérer l'élimination des ressources. (Et croyez-moi, l'écriture d'un C ++ destructor est magnitudes plus facile que d'écrire le droit Java finally, ou même une bonne méthode Dispose de C #).

Pourtant, parfois, une clause de finally serait cool. Peut-on le faire en C ++? Oui, nous pouvons! Et encore une fois avec une autre utilisation de RAII.

Conclusion: RAII est plus que la philosophie en C ++: Il est C ++

  

RAII? C'EST C ++ !!!

     

- C ++ développeur de outragéecommentaire, sans vergogne copié par un roi obscur Sparte et ses 300 amis

Lorsque vous atteignez un certain niveau d'expérience en C ++, vous commencez à penser en termes de RAII , en termes de construtors et exécution automatisés Destructeurs .

Vous commencez à penser en termes de champs , et les caractères deviennent { et } ceux des plus importants dans votre code.

Et presque tout est en parfaite harmonie termes de RAII: la sécurité d'exception, les mutex, les connexions de base de données, les requêtes de base de données, la connexion au serveur, horloges, poignées de systèmes d'exploitation, etc., et le dernier, mais non le moindre, la mémoire

.

La partie de base de données est non négligeable, comme, si vous acceptez de payer le prix, vous pouvez même écrire dans un « programmation transactionnelle » style, l'exécution des lignes et des lignes de code jusqu'à décider, dans le fin de compte, si vous voulez engager tous les changements, ou, à défaut, ayant tous les changements revenue (à condition que chaque ligne satisfaire au moins la garantie d'exception Strong). (Voir la deuxième partie de cette article Sutter Herb pour la programmation transactionnelle).

Et comme un casse-tête, tout va.

RAII est tellement partie de C ++, C ++ ne pouvait pas être C ++ sans elle.

Ceci explique pourquoi les développeurs C ++ expérimentés sont si épris de RAII, et pourquoi RAII est la première chose qu'ils recherchent en essayant une autre langue.

Et il explique pourquoi le Garbage Collector, tandis qu'un morceau de la technologie en magnificient elle-même, n'est pas si impressionnant du point de vue d'un développeur C ++:

  • RAII gère déjà la plupart des cas traités par un GC
  • A traite GC mieux que RAII avec des références circulaires sur les objets gérés purs (atténués par des utilisations intelligentes de pointeurs faibles)
  • Encore un GC est limitée à la mémoire, alors que RAII peut gérer tout type de ressource.
  • Comme décrit ci-dessus, RAII peut faire beaucoup, beaucoup plus ...

RAII utilise la sémantique C ++ de Destructeurs pour gérer les ressources. Par exemple, considérons un pointeur intelligent. Vous avez un constructeur paramétrés du pointeur qui initialise ce pointeur avec l'adresse de l'objet. Vous allouez un pointeur sur la pile:

SmartPointer pointer( new ObjectClass() );

Lorsque le pointeur intelligent est hors de portée du Destructeur de la classe de pointeur supprime l'objet connecté. Le pointeur est empiler-attribué et l'objet. - tas alloué

Il y a certains cas où RAII ne vous aide pas. Par exemple, si vous utilisez des pointeurs intelligents de comptage de références (comme boost :: shared_ptr) et de créer une structure semblable graphique avec un cycle, vous risquez de faire face à une fuite de mémoire parce que les objets dans un cycle empêcheront l'autre d'être libéré. Collecte des ordures ménagères contribuerait contre cela.

Je suis d'accord avec cpitis. Mais voudrais ajouter que les ressources peuvent être quelque chose non seulement la mémoire. La ressource peut être un fichier, une section critique, un fil ou une connexion de base de données.

Il est appelé l'acquisition des ressources est Initialisation parce que la ressource est acquise lorsque l'objet le contrôle de la ressource est construit, si le constructeur n'a pas (en raison d'une exception), la ressource n'est pas acquise. Puis, une fois l'objet est hors de portée de la ressource est libérée. c ++ garantit que tous les objets sur la pile qui ont été construits avec succès seront détruits (ce qui inclut les constructeurs de classes de base et les membres même si le constructeur super classe échoue).

Le rationnel derrière RAII est de faire exception d'acquisition des ressources en toute sécurité. Que toutes les ressources acquises sont correctement libérés, peu importe où se produit une exception. Toutefois, cela ne repose sur la qualité de la classe qui acquiert la ressource (ce doit être exception sûr et cela est difficile).

Je voudrais mettre un peu plus fortement alors les réponses précédentes.

RAII, Resource Acquisition Est Initialisation signifie que toutes les ressources acquises devraient être acquises dans le cadre de l'initialisation d'un objet. Cela interdit l'acquisition de ressources « nu ». La raison est que le nettoyage en C ++ fonctionne sur la base d'objets, pas d'appel de fonction base. Par conséquent, tout le nettoyage doit être fait par des objets, et non des appels de fonction. En ce sens, C ++ est orienté alors par exemple plus-objet Java. nettoyage Java est basé sur les appels de fonction dans les clauses de finally.

Le problème avec la collecte des ordures est que vous perdez la destruction déterministe qui est crucial pour RAII. Une fois une variable est hors de portée, il est au garbage collector lorsque l'objet sera récupéré. La ressource qui est détenue par l'objet continuera d'être détenu jusqu'à ce que la destructor est appelée.

RAII provient de l'allocation des ressources est Initialisation. Fondamentalement, cela signifie que, quand un constructeur se termine l'exécution, l'objet construit est entièrement initialisé et prêt à l'emploi. Il implique également que le destructor libérera des ressources (par exemple la mémoire, les ressources OS) appartenant à l'objet.

Par rapport aux langues déchets collectés / technologies (par exemple Java, .NET), C ++ permet un contrôle total de la vie d'un objet. Pour une pile alloué objet, vous saurez quand le destructeur de l'objet sera appelé (lorsque l'exécution est hors de la portée), chose qui est pas vraiment contrôlé en cas de collecte des ordures. Même en utilisant des pointeurs intelligents en C ++ (par exemple boost :: shared_ptr), vous saurez que quand il n'y a pas de référence à l'objet pointu, le destructeur de cet objet sera appelé.

  

Et comment pouvez-vous faire quelque chose sur la pile qui provoque le nettoyage de quelque chose qui vit sur le tas?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

Quand une instance de int_buffer entre en existence, il doit avoir une taille, et il attribuera la mémoire nécessaire. Quand il est hors de portée, il est destructeur. Ceci est très utile pour des choses comme des objets de synchronisation. Considérez

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.
  

En outre, les cas sont là où vous ne pouvez pas utiliser RAII?

Non, pas vraiment.

  

Avez-vous jamais vous vous trouvez qui souhaite pour la collecte des ordures? Au moins un collecteur de déchets que vous pouvez utiliser pour certains objets tout en laissant les autres gérer?

Jamais. Collecte des ordures ménagères résout un sous-ensemble très faible de la gestion dynamique des ressources.

Il y a déjà beaucoup de bonnes réponses, mais je voudrais simplement ajouter:
Une explication simple du RAII est que, dans C ++, un objet alloué sur la pile est détruite à chaque fois qu'il est hors de portée. Cela signifie, un destructor objets sera appelé et peut faire tout le nettoyage nécessaire.
Cela signifie que, si un objet est créé sans « nouveau », pas « supprimer » est nécessaire. Et cela est aussi l'idée derrière « pointeurs intelligents. » - ils se trouvent sur la pile, et enveloppe essentiellement un objet à base de tas

RAII est un acronyme pour l'acquisition des ressources est Initialisation.

Cette technique est très unique de C ++ en raison de leur soutien pour les constructeurs et destructeurs et presque automatiquement les constructeurs qui assortissent que les arguments sont passés ou le pire des cas le constructeur par défaut est appelé & Destructeurs si explicity fourni est appelé autrement celui par défaut qui est ajouté par le compilateur C de est appelé si vous n'avez pas écrit explicitement destructor pour une classe C ++. Cela se produit uniquement pour les objets C ++ qui sont gérés automatiquement - sens qui n'utilisent pas le magasin libre (mémoire allouée / utilisant de nouvelles désallouée, nouveau [] / supprimer, supprimer [] opérateurs C ++).

technique RAII fait usage de cette fonction d'objet géré automatiquement pour gérer les objets qui sont créés sur le tas / sans magasin en demandant explcitly plus de mémoire à l'aide de nouveaux / nouvelles [], qui devrait être explicitement détruit en appelant supprimer / effacer[]. La classe d'objets gérés automatiquement se terminera cet autre objet qui est créé sur le tas / mémoire libre magasin. Par conséquent, lorsque le constructeur de l'objet géré automatique est exécuté, l'objet enveloppé est créé sur le tas / mémoire libre magasin et lorsque la poignée de l'objet géré automatique est hors de portée, destructor de cet objet géré automatiquement est appelée automatiquement dans lequel l'enveloppe objet est détruit à l'aide supprimer. Avec des concepts OOP, si vous envelopper ces objets dans une autre classe portée privée, vous ne pas avoir accès aux classes enveloppées membres et méthodes et ceci est la raison pour laquelle des pointeurs intelligents (aka gérer les classes) sont conçus pour. Ces pointeurs intelligents exposent l'objet enveloppé comme objet typé au monde et il externe en permettant d'invoquer les membres / méthodes que l'objet de la mémoire exposée est composée de. Notez que les pointeurs intelligents ont différentes saveurs en fonction de différents besoins. Vous devez vous référer à la programmation moderne C ++ par Andrei Alexandrescu ou stimuler shared_ptr.hpp de la bibliothèque (www.boostorg) mise en œuvre / documentation pour en savoir plus à ce sujet. Espérons que cela vous aide à comprendre RAII.

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