Comprendre la signification du terme et du concept - RAII (Resource Acquisition is Initialization)


110

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

Je ne connais un peu. Je crois que cela signifie «L'acquisition de ressources est l'initialisation». Cependant, ce nom ne correspond pas à ma compréhension (peut-être incorrecte) de ce qu'est RAII: j'ai l'impression que RAII est un moyen d'initialiser des objets sur la pile de sorte que, lorsque ces variables sortent de la portée, les destructeurs seront automatiquement être appelé provoquant le nettoyage des ressources.

Alors pourquoi n'est-ce pas appelé "utiliser la pile pour déclencher le nettoyage" (UTSTTC :)? Comment en arriver à "RAII"?

Et comment pouvez-vous créer quelque chose sur la pile qui provoquera le nettoyage de quelque chose qui vit sur le tas? De plus, y a-t-il des cas où vous ne pouvez pas utiliser RAII? Vous êtes-vous déjà retrouvé à souhaiter la collecte des ordures? Au moins un garbage collector que vous pourriez utiliser pour certains objets tout en laissant d'autres être gérés?

Merci.


27
UTSTTC? Je l'aime! C'est beaucoup plus intuitif que RAII. RAII est mal nommé, je doute qu'un programmeur C ++ conteste cela. Mais ce n'est pas facile de changer. ;)
jalf

10
Voici le point de vue de Stroustrup sur la question: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi

3
@sbi: Quoi qu'il en soit, +1 sur votre commentaire juste pour la recherche historique. Je crois qu'avoir le point de vue de l'auteur (B. Stroustrup) sur le nom d'un concept (RAII) est suffisamment intéressant pour avoir sa propre réponse.
paercebal

1
@paercebal: Recherche historique? Maintenant tu m'as fait me sentir très vieux. :(Je lisais tout le fil, à l'époque, et je ne me considérais même pas comme un débutant en C ++!
sbi

3
+1, j'étais sur le point de poser la même question, content de ne pas être le seul à comprendre le concept mais à ne pas comprendre le nom. Il semble qu'il aurait dû s'appeler RAOI - Acquisition de ressources à l'initialisation.
laurent

Réponses:


132

Alors pourquoi n'est-ce pas appelé "utiliser la pile pour déclencher le nettoyage" (UTSTTC :)?

RAII vous dit quoi faire: Acquérir votre ressource chez un constructeur! J'ajouterais: une ressource, un constructeur. UTSTTC n'est qu'une application de cela, RAII est bien plus.

La gestion des ressources est nul. Ici, la ressource est tout ce qui doit être nettoyé après utilisation. Les études de projets sur de nombreuses plates-formes montrent que la majorité des bogues sont liés à la gestion des ressources - et c'est particulièrement mauvais sous Windows (en raison des nombreux types d'objets et d'allocateurs).

En C ++, la gestion des ressources est particulièrement compliquée en raison de la combinaison d'exceptions et de modèles (de style C ++). Pour un aperçu 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 cela, RAII peut résoudre de nombreux problèmes désagréables dont le programmeur moyen pourrait même ne pas être au courant. Voici quelques exemples au-delà du "mes variables locales seront détruites à chaque retour".

Commençons par une FileHandleclasse 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 (avec une exception), aucune autre fonction membre - pas même le destructeur - n'est appelée.

RAII évite d'utiliser des objets dans un état non valide. cela facilite déjà la vie avant même que nous n'utilisions l'objet.

Voyons maintenant les objets temporaires:

void CopyFileData(FileHandle source, FileHandle dest);

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

Il y a trois cas d'erreur à 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 une implémentation non-RAII, Foodevrait traiter les trois cas de manière explicite.

RAII libère les ressources qui ont été acquises, même lorsque plusieurs ressources sont acquises dans une seule instruction.

Maintenant, agrégons quelques 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 originalle constructeur de s échoue (car il filename1n'a pas pu être ouvert), duplexle constructeur de échoue (car filename2n'a pas pu être ouvert) ou si l'écriture dans les fichiers à Loggerl' intérieur du corps du constructeur échoue. Dans aucun de ces cas, Loggerle destructeur de ne sera appelé - nous ne pouvons donc pas compter sur Loggerle destructeur de pour libérer les fichiers. Mais s'il a originalété construit, son destructeur sera appelé lors du nettoyage du Loggerconstructeur.

RAII simplifie le nettoyage après une construction partielle.


Points négatifs:

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

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

Je n'ai jamais vraiment souhaité la collecte des ordures. Quand je fais du C #, je ressens parfois un moment de bonheur dont je n'ai tout simplement pas besoin de m'en soucier, mais bien plus, tous les jouets cool qui peuvent être créés par destruction déterministe me manquent. (l'utilisation IDisposablene le coupe pas.)

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 nous sommes trompés en équilibrant soigneusement les pointeurs forts et faibles, mais chaque fois que nous voulons changer quelque chose, nous devons étudier un grand tableau des relations. GC aurait peut-être été meilleur, mais certains des composants contenaient des ressources qui devraient être libérées dès que possible.


Une note sur l'exemple FileHandle: il n'était pas destiné à être complet, juste un échantillon - mais s'est avéré incorrect. Merci Johannes Schaub pour l'avoir signalé et FredOverflow pour l'avoir transformé en une solution C ++ 0x correcte. Au fil du temps, je me suis installé avec l'approche documentée ici .


1
+1 Pour indiquer que GC et ASAP ne sont pas maillés. Ça ne fait pas mal souvent mais quand c'est pas facile de diagnostiquer: /
Matthieu M.

10
Une phrase en particulier que j'ai négligée lors de lectures précédentes. Vous avez dit que "RAII" vous disait: "Acquérez vos ressources à l'intérieur des constructeurs." Cela a du sens et est presque une paraphrase mot pour mot de "RAII". Maintenant, je comprends encore mieux (je vous voterais à nouveau si je le pouvais :)
Charlie Flowers

2
Un avantage majeur de GC est qu'un framework d'allocation de mémoire peut empêcher la création de références pendantes en l'absence de code "unsafe" (si le code "unsafe" est autorisé, bien sûr, le framework ne peut rien empêcher). GC est également souvent supérieur à RAII lorsqu'il s'agit d' objets immuables partagés tels que des chaînes qui n'ont souvent pas de propriétaire clair et ne nécessitent aucun nettoyage. Il est malheureux que davantage de frameworks ne cherchent pas à combiner GC et RAII, car la plupart des applications auront un mélange d'objets immuables (où GC serait le meilleur) et d'objets qui ont besoin d'être nettoyés (où RAII est le meilleur).
supercat du

@supercat: J'aime généralement GC - mais cela ne fonctionne que pour les ressources que GC "comprend". Par exemple, le GC .NET ne connaît pas le coût des objets COM. Lorsqu'il s'agit simplement de les créer et de les détruire en boucle, il sera heureux de laisser l'application fonctionner dans le sol en ce qui concerne l'espace d'adressage ou la mémoire virtuelle - tout ce qui vient en premier - sans même penser à peut-être faire un GC. --- de plus, même dans un environnement parfaitement GC, le pouvoir de la destruction déterministe me manque toujours: vous pouvez appliquer le même modèle à d'autres artificats, par exemple en affichant des éléments d'interface utilisateur sous des conditions certian.
peterchen

@peterchen: Une chose qui, je pense, est absente de beaucoup de réflexions liées à la POO est le concept de propriété d'objets. Le suivi de la propriété est souvent clairement nécessaire pour les objets avec des ressources, mais est également souvent nécessaire pour les objets mutables sans ressources. En général, les objets doivent encapsuler leur état mutable soit dans des références à des objets immuables éventuellement partagés, soit dans des objets mutables dont ils sont le propriétaire exclusif. Une telle propriété exclusive n'implique pas nécessairement un accès en écriture exclusif, mais si elle est Foopropriétaire Baret la Bozmute, ...
supercat

42

Il existe d'excellentes réponses, alors j'ajoute simplement des choses oubliées.

0. RAII concerne les étendues

RAII concerne les deux:

  1. acquérir une ressource (quelle que soit la ressource) dans le constructeur et la désacquérir dans le destructeur.
  2. avoir le constructeur exécuté lorsque la variable est déclarée, et le destructeur automatiquement exécuté lorsque la variable sort de la portée.

D'autres ont déjà répondu à ce sujet, je ne vais donc pas m'étendre davantage.

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

MONSIEUR JOURDAIN: Quoi! Quand je dis: «Nicole, apporte-moi mes pantoufles et donne-moi mon dernier verre», c'est de la prose?

MAÎTRE DE PHILOSOPHIE: Oui, Monsieur.

MONSIEUR JOURDAIN: Depuis plus de quarante ans, je parle de la prose sans rien en savoir, et je vous suis bien obligé de m'avoir appris cela.

- Molière: Le gentilhomme de la classe moyenne, acte 2, scène 4

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

void foo()
{
   // etc.

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

   // etc.
}

... utilise déjà RAII: L'acquisition du mutex se fait dans le mot-clé ( synchronizedou lock), et la désacquisition se fera à la sortie du scope.

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

L'avantage de C ++ sur Java et C # ici est que tout peut être fait en utilisant RAII. Par exemple, il n'y a pas d'équivalent intégré direct de synchronizedni locken C ++, mais nous pouvons toujours les avoir.

En C ++, il s'écrirait:

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 à la manière Java / C # (en utilisant des macros C ++):

void foo()
{
   // etc.

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

   // etc.
}

2. RAII a d'autres utilisations

WHITE RABBIT: [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 (version Disney, 1951)

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

Par exemple, vous pouvez écrire un objet compteur (je laisse cela comme un exercice) et l'utiliser simplement 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, de manière 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 le C ++ manque- finallyt-il?

[CRIER] C'est le compte à rebours final !

- Europe: le compte à rebours final (désolé, j'étais à court de citations, ici ... :-)

La finallyclause est utilisée en C # / Java pour gérer l'élimination des ressources en cas de sortie d'étendue (via une returnexception ou une exception levée).

Les lecteurs de spécifications astucieux auront remarqué que C ++ n'a pas de clause finally. Et ce n'est pas une erreur, car C ++ n'en a pas besoin, car RAII gère déjà l'élimination des ressources. (Et croyez-moi, écrire un destructeur C ++ est plus facile que d'écrire la bonne clause Java finally, ou même la méthode Dispose correcte d'un C #).

Pourtant, parfois, une finallyclause serait cool. Pouvons-nous le faire en C ++? Oui nous pouvons! Et encore une fois avec une utilisation alternative de RAII.

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

RAII? CECI EST C ++ !!!

- Commentaire indigné du développeur C ++, copié sans vergogne par un obscur roi 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 d' exécution automatisée des constructeurs et des destructeurs .

Vous commencez à penser en termes de portées , et les caractères {et }deviennent l'un des plus importants de votre code.

Et presque tout convient en termes de RAII: sécurité des exceptions, mutex, connexions de base de données, requêtes de base de données, connexion au serveur, horloges, poignées de système d'exploitation, etc., et enfin, la mémoire.

La partie base de données n'est pas négligeable, car, si vous acceptez de payer le prix, vous pouvez même écrire dans un style « programmation transactionnelle », en exécutant des lignes et des lignes de code jusqu'à décider, au final, si vous voulez valider tous les changements , ou, si ce n'est pas possible, avoir toutes les modifications annulées (tant que chaque ligne satisfait au moins la garantie d'exception forte). (voir la deuxième partie de cet article de Herb's Sutter pour la programmation transactionnelle).

Et comme un puzzle, tout va bien.

RAII fait tellement partie de C ++ que C ++ ne pourrait pas être C ++ sans lui.

Cela explique pourquoi les développeurs C ++ expérimentés sont si amoureux de RAII, et pourquoi RAII est la première chose qu'ils recherchent lorsqu'ils essaient un autre langage.

Et cela explique pourquoi le Garbage Collector, bien qu'il s'agisse d'une technologie magnifique en soi, n'est pas si impressionnant du point de vue d'un développeur C ++:

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

Un fan de Java: je dirais que GC est bien plus utile que RAII car il gère toute la mémoire et vous libère de nombreux bugs potentiels. Avec GC, vous pouvez créer des références circulaires, retourner et stocker des références et il est difficile de se tromper (stocker une référence à un objet supposé de courte durée allonge sa durée de vie, ce qui est une sorte de fuite de mémoire, mais c'est le seul problème) . La gestion des ressources avec GC ne fonctionne pas, mais la plupart des ressources d'une application ont un cycle de vie trivial, et les quelques ressources restantes ne sont pas un gros problème. J'aurais aimé pouvoir avoir à la fois GC et RAII, mais cela semble impossible.
maaartinus le

16

1
Certaines d'entre elles correspondent parfaitement à ma question, mais une recherche ne les a pas révélées, ni la liste des «questions connexes» qui apparaît après que vous ayez saisi une nouvelle question. Merci pour les liens.
Charlie Flowers

1
@Charlie: La construction de la recherche est très faible à certains égards. L'utilisation de la syntaxe de la balise ("[topic]") est très utile, et de nombreuses personnes utilisent google ...
dmckee --- ex-moderator kitten

10

RAII utilise la sémantique des destructeurs C ++ pour gérer les ressources. Par exemple, considérons un pointeur intelligent. Vous avez un constructeur paramétré 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 sort de la portée, le destructeur de la classe pointeur supprime l'objet connecté. Le pointeur est alloué par pile et l'objet - alloué par tas.

Il y a certains cas où RAII n'aide pas. Par exemple, si vous utilisez des pointeurs intelligents de comptage de références (comme boost :: shared_ptr) et créez une structure de type graphique avec un cycle, vous risquez de faire face à une fuite de mémoire car les objets d'un cycle s'empêcheront mutuellement d'être libérés. Le ramassage des ordures aiderait contre cela.


2
Il devrait donc s'appeler UCDSTMR :)
Daniel Daranas

Sur une seconde réflexion, je pense que UDSTMR est plus approprié. Le langage (C ++) est donné, donc la lettre "C" n'est pas nécessaire dans l'acronyme. UDSTMR signifie Utilisation de la sémantique du destructeur pour gérer les ressources.
Daniel Daranas

9

Je voudrais le dire un peu plus fortement que les réponses précédentes.

RAII, Resource Acquisition Is Initialization signifie que toutes les ressources acquises doivent être acquises dans le contexte de l'initialisation d'un objet. Cela interdit l'acquisition de ressources «nues». Le raisonnement est que le nettoyage en C ++ fonctionne sur la base de l'objet, pas sur la base de l'appel de fonction. Par conséquent, tout nettoyage doit être effectué par des objets et non par des appels de fonction. Dans ce sens, C ++ est plus orienté objet que Java. Le nettoyage Java est basé sur les appels de fonction dans les finallyclauses.


Très bonne réponse. Et "initialisation d'un objet" signifie "constructeurs", oui?
Charlie Flowers

@Charlie: oui, surtout dans ce cas.
MSalters

8

Je suis d'accord avec le cpitis. Mais j'aimerais 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 thread ou une connexion à une base de données.

Elle est appelée Acquisition de ressources est une initialisation car la ressource est acquise lorsque l'objet contrôlant la ressource est construit. Si le constructeur a échoué (c'est-à-dire en raison d'une exception), la ressource n'est pas acquise. Ensuite, une fois que l'objet est hors de portée, la ressource est libérée. c ++ garantit que tous les objets de la pile qui ont été construits avec succès seront détruits (cela inclut les constructeurs des classes de base et des membres même si le constructeur de la super classe échoue).

Le raisonnement derrière RAII est de sécuriser l'exception d'acquisition de ressources. Que toutes les ressources acquises sont correctement libérées, peu importe où une exception se produit. Cependant, cela dépend de la qualité de la classe qui acquiert la ressource (cela doit être exceptionnellement sûr et c'est difficile).


Excellent, merci d'avoir expliqué la raison d'être du nom. Si je comprends bien, vous pourriez paraphraser RAII comme suit: «N'acquérez jamais de ressource par un autre mécanisme que l'initialisation (basée sur le constructeur)». Oui?
Charlie Flowers

Oui, c'est ma politique, mais je me méfie beaucoup d'écrire mes propres classes RAII car elles doivent être exceptionnellement sûres. Lorsque je les écris, j'essaie d'assurer la sécurité des exceptions en réutilisant d'autres classes RAII écrites par des experts.
iain

Je ne les ai pas trouvés difficiles à écrire. Si vos classes sont suffisamment petites, elles ne sont pas du tout difficiles.
Rob K

7

Le problème avec le garbage collection est que vous perdez la destruction déterministe qui est cruciale pour RAII. Une fois qu'une variable sort de la portée, c'est au ramasse-miettes quand l'objet sera récupéré. La ressource détenue par l'objet continuera d'être conservée jusqu'à ce que le destructeur soit appelé.


4
Le problème n'est pas seulement le déterminisme. Le vrai problème est que les finaliseurs (nommage java) gênent GC. GC est efficace car il ne rappelle pas les objets morts, mais les ignore plutôt dans l'oubli. Les GC doivent suivre les objets avec les finaliseurs d'une manière différente pour garantir qu'ils sont appelés
David Rodríguez - dribeas

1
sauf en java / c # vous nettoieriez probablement dans un bloc finally plutôt que dans un finaliseur.
jk.

4

RAII provient de l'allocation des ressources est l'initialisation. Fondamentalement, cela signifie que lorsqu'un constructeur termine l'exécution, l'objet construit est entièrement initialisé et prêt à être utilisé. Cela implique également que le destructeur libérera toutes les ressources (par exemple, la mémoire, les ressources du système d'exploitation) appartenant à l'objet.

Comparé aux langages / technologies garbage collection (par exemple Java, .NET), C ++ permet un contrôle total de la vie d'un objet. Pour un objet alloué par pile, vous saurez quand le destructeur de l'objet sera appelé (lorsque l'exécution sort du cadre), chose qui n'est pas vraiment contrôlée en cas de garbage collection. Même en utilisant des pointeurs intelligents en C ++ (par exemple, boost :: shared_ptr), vous saurez que lorsqu'il n'y a pas de référence à l'objet pointé, le destructeur de cet objet sera appelé.


3

Et comment pouvez-vous créer quelque chose sur la pile qui provoquera 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.

Lorsqu'une instance de int_buffer entre en existence, elle doit avoir une taille, et elle allouera la mémoire nécessaire. Lorsqu'il sort de la portée, son destructeur est appelé. Ceci est très utile pour des choses comme les objets de synchronisation. Considérer

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.

De plus, y a-t-il des cas où vous ne pouvez pas utiliser RAII?

Non, pas vraiment.

Vous êtes-vous déjà retrouvé à souhaiter la collecte des ordures? Au moins un garbage collector que vous pourriez utiliser pour certains objets tout en laissant d'autres être gérés?

Jamais. Le garbage collection ne résout qu'un très petit sous-ensemble de la gestion dynamique des ressources.


J'ai très peu utilisé Java et C #, donc je ne l'ai jamais manqué, mais GC a certainement restreint mon style en matière de gestion des ressources lorsque je devais les utiliser, car je ne pouvais pas utiliser RAII.
Rob K

1
J'ai beaucoup utilisé C # et je suis d'accord avec vous à 100%. En fait, je considère un CG non déterministe comme un passif dans une langue.
Nemanja Trifunovic

2

Il y a déjà beaucoup de bonnes réponses ici, mais je voudrais juste ajouter:
Une explication simple de RAII est que, en C ++, un objet alloué sur la pile est détruit chaque fois qu'il est hors de portée. Cela signifie qu'un destructeur d'objets sera appelé et pourra effectuer tous les nettoyages nécessaires.
Cela signifie que si un objet est créé sans "nouveau", aucune "suppression" n'est requise. Et c'est aussi l'idée derrière les "pointeurs intelligents" - ils résident sur la pile et encapsulent essentiellement un objet basé sur le tas.


1
Non, ils ne le font pas. Mais avez-vous une bonne raison de créer un pointeur intelligent sur le tas? À propos, le pointeur intelligent n'était qu'un exemple des domaines où RAII peut être utile.
E Dominique

1
Peut-être que mon utilisation de «pile» contre «tas» est un peu bâclée - par un objet sur «la pile», je voulais dire n'importe quel objet local. Il peut naturellement faire partie d'un objet, par exemple sur le tas. Par "créer un pointeur intelligent sur le tas", je voulais utiliser new / delete sur le pointeur intelligent lui-même.
E Dominique

1

RAII est un acronyme pour Resource Acquisition Is Initialization.

Cette technique est tout à fait unique au C ++ en raison de leur prise en charge à la fois des constructeurs et des destructeurs et presque automatiquement les constructeurs qui correspondent aux arguments passés ou dans le pire des cas, le constructeur par défaut est appelé & destructeurs si l'explicité fournie est appelée sinon celle par défaut qui est ajouté par le compilateur C ++ est appelé si vous n'avez pas écrit explicitement un destructeur pour une classe C ++. Cela ne se produit que pour les objets C ++ gérés automatiquement, c'est-à-dire qui n'utilisent pas le magasin libre (mémoire allouée / désallouée à l'aide des opérateurs C ++ new, new [] / delete, delete []).

La technique RAII utilise cette fonction d'objet géré automatiquement pour gérer les objets créés sur le tas / magasin libre en demandant explicitement plus de mémoire en utilisant new / new [], qui devrait être explicitement détruite en appelant delete / delete [] . La classe de l'objet géré automatiquement encapsulera cet autre objet créé sur le tas / mémoire libre. Par conséquent, lorsque le constructeur de l'objet géré automatiquement est exécuté, l'objet encapsulé est créé sur le tas / mémoire libre et lorsque le handle de l'objet géré automatiquement sort de la portée, le destructeur de cet objet géré automatiquement est appelé automatiquement dans lequel le l'objet est détruit à l'aide de delete. Avec les concepts POO, si vous enveloppez de tels objets dans une autre classe dans une portée privée, vous n'auriez pas accès aux membres et méthodes des classes encapsulées & c'est la raison pour laquelle les pointeurs intelligents (aka classes de poignée) sont conçus pour. Ces pointeurs intelligents exposent l'objet encapsulé en tant qu'objet typé au monde externe et là-bas en permettant d'invoquer tous les membres / méthodes dont l'objet mémoire exposé est composé. Notez que les pointeurs intelligents ont différentes saveurs en fonction de différents besoins. Vous devriez vous référer à la programmation C ++ moderne d'Andrei Alexandrescu ou à l'implémentation / documentation de la bibliothèque boost (www.boostorg) shared_ptr.hpp pour en savoir plus. J'espère que cela vous aidera à comprendre RAII. Vous devriez vous référer à la programmation C ++ moderne d'Andrei Alexandrescu ou à l'implémentation / documentation de la bibliothèque boost (www.boostorg) shared_ptr.hpp pour en savoir plus. J'espère que cela vous aidera à comprendre RAII. Vous devriez vous référer à la programmation C ++ moderne d'Andrei Alexandrescu ou à l'implémentation / documentation de la bibliothèque boost (www.boostorg) shared_ptr.hpp pour en savoir plus. J'espère que cela vous aidera à comprendre RAII.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.