109 votes

Comprendre le sens du terme et le concept - RAII (Resource Acquisition est initialisation)

Pourriez-vous les développeurs C++ merci de nous donner une bonne description de ce RAII est, pourquoi il est important, et si oui ou non il pourrait avoir une pertinence pour d'autres langues?

Je ne sais un peu plus. Je crois qu'il tient pour "l'Acquisition de Ressources est d'Initialisation". Cependant, ce nom n'est pas jive avec mon (peut-être inexacte), la compréhension de ce que RAII est: j'ai l'impression que le RAII est un moyen d'initialisation des objets sur la pile de telle sorte que, lorsque ces variables hors de portée, les destructeurs sera automatiquement appelée à l'origine des ressources pour être nettoyé.

Alors pourquoi n'est-ce pas appelé "à l'aide de la pile pour déclencher de nettoyage" (UTSTTC:)? Comment voulez-vous obtenir à partir de là à "RAII"?

Et comment pouvez-vous faire quelque chose sur la pile, ce qui entraîne le nettoyage de quelque chose qui vit sur le tas? Aussi, existe-il des cas où vous ne pouvez pas utiliser RAII? Vous trouvez-vous jamais souhaitant pour la collecte des ordures? Au moins un garbage collector que vous pouvez utiliser pour certains objets, tout en laissant les autres à être géré?

Merci.

130voto

peterchen Points 21792

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

RAII est de vous dire quoi faire: Acquérir vos ressources dans un constructeur! J'ajouterais: une ressource, un constructeur. UTSTTC est juste une application, RAII est beaucoup plus.

La Gestion des ressources suce. Ici, la ressource est quelque chose qui a besoin de nettoyage après utilisation. Études de projets à travers de nombreuses plates-formes révèlent que la majorité des bugs liés à la gestion des ressources - et c'est particulièrement mauvais sur Windows (en raison des nombreux types d'objets et les allocateurs).

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


C++ garantit que le destructeur est appelé si et seulement si le constructeur a réussi. En s'appuyant sur ce que, RAII peut résoudre de nombreux problèmes nasty le programmeur moyen pourrait même ne pas être conscients de. Voici quelques exemples au-delà de l' "mon variables locales seront détruits chaque fois que j'en retour".

Laissez-nous commencer avec un peu trop simpliste FileHandle classe 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 (avec une exception), aucun autre membre de la fonction, même pas le destructeur - est appelée.

RAII évite d'utiliser des objets dans un état non valide. déjà, elle rend la vie plus facile avant même d'utiliser 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 de traiter: aucun fichier ne peut être ouvert, un seul fichier peut être ouvert, les deux fichiers peuvent être ouverts, mais la copie des fichiers a échoué. Dans un non-RAII mise en œuvre, Foo aurait pour gérer tous les trois cas explicitement.

RAII libère les ressources qui ont été acquis, même lorsque plusieurs ressources sont acquises dans un délai d'un énoncé.

Maintenant, laissez-nous d'agrégation de certains 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 originals'constructeur échoue (car filename1 ne peut pas être ouvert), duplexs'constructeur échoue (car filename2 ne peut pas être ouvert), ou l'écriture des fichiers à l'intérieur d' Loggers'corps du constructeur échoue. Dans tous ces cas, Logger's destructeur va pas être appelé - donc on ne peut pas compter sur Logger's destructeur pour libérer les fichiers. Mais si original a été construit, son destructeur sera appelé pendant le nettoyage de la Logger constructeur.

RAII simplifie le nettoyage après partiel de la construction.


Points négatifs:

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

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

Je n'ai jamais voulu pour la collecte des ordures vraiment. Quand je fais du C# j'ai parfois l'impression d'un moment de bonheur que je n'ai juste pas besoin de soins, mais bien plus, je m'ennuie toute la fraîcheur des jouets qui peuvent être créés par déterministe de la destruction. (à l'aide d' IDisposable a tout simplement pas le couper.)

J'ai eu un en particulier la complexité de la structure qui pourraient avoir bénéficié de GC, où "simple" pointeurs intelligents serait la cause des références circulaires sur plusieurs classes. Nous confuse par trouver un juste équilibre entre les forts et les faibles pointeurs, mais chaque fois que nous voulons changer quelque chose, nous devons étudier une grande relation graphique. GC aurait été mieux, mais certains composants de la tenue de ressources qui doit être communiqué le plus tôt possible.


Une note sur le Descripteur de fichier de l'échantillon: Il n'était pas destiné à être complet, juste un échantillon, mais s'est avéré inexact. Merci Johannes Schaub pour souligner et FredOverflow pour le transformer en un bon de C++0x solution. Au fil du temps, j'ai réglé avec l'approche décrite ici.

42voto

paercebal Points 38526

Il y a d'excellentes réponses là-bas, donc j'ai juste ajouter un peu de choses oubliées.

0. RAII est sur les étendues

RAII est sujet à la fois:

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

D'autres déjà répondu à ce sujet, donc je ne m'étendrai pas.

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

MONSIEUR JOURDAIN: Ce qui! Quand je dis: "Nicole, apportez-moi mes pantoufles, et me donnez mon bonnet de nuit," c'est de la prose?

La PHILOSOPHIE de MAÎTRE: Oui, Monsieur.

MONSIEUR JOURDAIN: depuis plus De quarante ans, j'ai parlé de la prose sans rien y connaître, et je suis bien obligé de vous pour avoir enseigné moi.

- Molière: La Classe Moyenne Gentilhomme, Acte 2, Scène 4

Comme Monsieur Jourdain fait avec de la prose, C# et même Java personnes utilisent déjà RAII, mais dans les voies cachées. Par exemple, le code Java suivant (qui est écrit de la même façon 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.
}

... est déjà à l'aide de RAII: Le mutex acquisition se fait dans le mot-clé (synchronized ou lock), et de l'onu-l'acquisition se fait au moment de la sortie de la portée.

C'est tellement naturel dans sa notation il nécessite presque pas d'explication, même pour les personnes qui n'ont jamais entendu parler de RAII.

L'avantage du C++ a plus de Java et C# ici, c'est que tout peut être fait en utilisant le RAII. Par exemple, il n'existe pas de construire-dans l'équivalent d' synchronized ni lock en C++, mais on peut encore 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.
}

ce qui peut facilement être écrite en Java/C# chemin (à l'aide de C++ macros):

void foo()
{
   // etc.

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

   // etc.
}

2. RAII ont d'autres utilisations

LAPIN BLANC: [en chantant] 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 (Disney version, 1951)

Vous savez, quand le constructeur sera appelé (à l'objet de la déclaration), et vous savez quand son correspondant destructeur sera appelé (à la sortie de la portée), de sorte que vous pouvez écrire presque magique, code avec, mais une ligne. Bienvenue au C++ pays des merveilles (au moins, à partir d'un développeur C++, du point de vue).

Par exemple, vous pouvez écrire un objet compteur (je laisse ça comme un exercice) et de l'utiliser simplement en déclarant la variable, comme le verrou de l'objet 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
}

ce qui, évidemment, peut être écrite, encore une fois, la Java/C# 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 est-ce que C++ manque finally?

[En CRIANT] C'est la finale de compte à rebours!

- Europe: Le compte à Rebours Final (désolé, j'étais hors de citations, ici... :-)

L' finally clause est utilisée en C#/Java pour gérer les ressources de l'élimination dans le cas de l'étendue de sortie (que ce soit par le biais d'un return ou la levée d'une exception).

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

Encore, parfois, une finally clause serait cool. On peut le faire en C++? Oui, nous le pouvons! Et encore avec une utilisation alternative de RAII.

Conclusion: RAII est plus qu'une philosophie en C++: C++

RAII? C'EST LE C++!!!

- Développeur C++ est indigné commentaire, copié sans vergogne par un obscur Sparte roi et de 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 destructeurs à l'exécution automatique.

Vous commencez à penser en termes de portées, et l' { et } caractères sont ceux des plus important dans votre code.

Et presque tout se tient droit en termes de RAII: l'exception de la sécurité, mutex, de connexions de base de données, base de données des demandes de connexion au serveur, les horloges, les OS poignées, etc., et le dernier, mais pas moins, de mémoire.

La base de données n'est pas négligeable, que, si vous acceptez de payer le prix, vous pouvez même écrire dans un "transactionnelle, la programmation de" style", de l'exécution des lignes et des lignes de code jusqu'à ce que de décider, en fin de compte, si vous voulez enregistrer toutes les modifications, ou, si pas possible, en ayant toutes les modifications qui revient (à condition que chaque ligne satisfaire au moins à la Forte Exception de Garantie). (voir la deuxième partie de cette plante Sutter article pour le transactionnel de programmation).

Et comme un puzzle, tout se tient.

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

C'est ce qui explique pourquoi connu les développeurs C++ sont tellement épris avec RAII, et pourquoi RAII est la première chose que l'on recherche lorsque l'on essaie une autre langue.

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

  • RAII déjà gère la plupart des cas, pris en charge par un GC
  • Un GC traite mieux que RAII avec des références circulaires sur pur objets gérés (atténué par smart utilise de la faiblesse des pointeurs)
  • Encore UN GC est limitée à la mémoire, alors que RAII peut gérer n'importe quel type de ressource.
  • Comme décrit ci-dessus, RAII peut faire beaucoup, beaucoup plus...

16voto

10voto

sharptooth Points 93379

RAII est à l'aide de C++ destructeurs de la sémantique à la gestion des ressources. Prenons l'exemple d'un pointeur intelligent. Vous avez un constructeur paramétré de le pointeur qui initialise le pointeur avec l'adresse de l'objet. Vous allouer un pointeur sur la pile:

SmartPointer pointer( new ObjectClass() );

Lorsque le pointeur intelligent est hors de portée, le destructeur de le pointeur de la classe supprime l'objet connecté. Le pointeur de pile-alloués et l'objet allouées sur la pile.

Il y a certains cas, lorsque le RAII ne l'aide pas. Par exemple, si vous utilisez de comptage de références pointeurs intelligents (comme boost::shared_ptr) et créer un graphique-comme la structure avec un cycle, vous risquez face à une fuite de mémoire parce que les objets d'un cycle d'empêcher les uns les autres d'être publié. La collecte des ordures aiderait à cet égard.

8voto

iain Points 4876

Je suis d'accord avec cpitis. Mais voudrais ajouter que les ressources peuvent être n'importe quoi, pas seulement de 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 de Ressources Est d'Initialisation, car la ressource est acquise que lorsque l'objet le contrôle de la ressource est construit, Si le constructeur a échoué (c'est à dire en raison d'une exception) la ressource n'est pas acquis. Ensuite, une fois que l'objet est hors de portée de la ressource est libérée. c++ garantit que tous les objets sur la pile qui a été construit avec succès seront détruits (ce qui inclut les constructeurs des classes de base et les membres, même si le super constructeur de la classe échoue).

Le rationnel derrière RAII est de faire l'acquisition de ressources exception coffre-fort. Que toutes les ressources acquises sont libérés correctement n'importe où une exception se produit. Toutefois, cela ne s'appuient sur la qualité de la classe que l'acquisition de la ressource (ce doit être une exception sûr et c'est dur).

Prograide.com

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

Powered by:

X