33 votes

Les programmeurs d'autres langages, en dehors du C++, utilisent-ils, connaissent-ils ou comprennent-ils le RAII ?

J'ai remarqué que le RAII a fait l'objet de beaucoup d'attention sur Stackoverflow, mais dans mes cercles (principalement C++), le RAII est si évident que c'est comme demander ce qu'est une classe ou un destructeur.

Je suis donc vraiment curieux de savoir si c'est parce que je suis entouré quotidiennement de programmeurs C++ purs et durs, et que RAII n'est pas si connu en général (y compris en C++), ou si toutes ces questions sur Stackoverflow sont dues au fait que je suis maintenant en contact avec des programmeurs qui n'ont pas grandi avec le C++, et que dans d'autres langues, les gens n'utilisent pas/ne connaissent pas RAII ?

2 votes

Une fois de plus, SO prouve sa valeur. J'ai généralement tendance à programmer de cette façon, mais je ne savais pas que cela avait été formalisé et appelé RAII. Merci.

2 votes

Les programmeurs BASIC pensent-ils à OEG1K (On Error Goto 1000) ?

0 votes

D'autres langues utilisent parfois le idiome d'exécution pour obtenir un comportement similaire.

25voto

jalf Points 142628

Il y a de nombreuses raisons pour lesquelles le RAII n'est pas plus connu. Tout d'abord, le nom n'est pas particulièrement évident. Si je ne savais pas déjà ce qu'est RAII, je ne le devinerais certainement pas à partir de son nom. (L'acquisition de ressources est une initialisation ? Qu'est-ce que cela a à voir avec le destructeur ou le nettoyage, qui est ce que l'on appelle l'initialisation ? vraiment caractérise RAII ?)

Une autre raison est qu'il ne fonctionne pas aussi bien dans les langages sans nettoyage déterministe.

En C++, nous savons exactement quand le destructeur est appelé, nous connaissons l'ordre dans lequel les destructeurs sont appelés, et nous pouvons les définir pour faire ce que nous voulons.

Dans la plupart des langages modernes, tout est ramassé à la source, ce qui rend la RAII plus difficile à mettre en œuvre. Il n'y a aucune raison pour qu'il ne soit pas possible d'ajouter des extensions RAII à, disons, C#, mais ce n'est pas aussi évident qu'en C++. Mais comme d'autres l'ont mentionné, Perl et d'autres langages supportent RAII malgré le garbage collector.

Cela dit, il est toujours possible de créer votre propre wrapper de type RAII en C# ou dans d'autres langages. Je l'ai fait en C# il y a quelque temps. Je devais écrire quelque chose pour m'assurer qu'une connexion à une base de données était fermée immédiatement après utilisation, une tâche que tout programmeur C++ verrait comme un candidat évident pour RAII. Bien sûr, nous pourrions tout envelopper dans using -chaque fois que nous utilisons une connexion à la base de données, mais c'est tout simplement désordonné et source d'erreurs.

Ma solution était d'écrire une fonction d'aide qui prenait un délégué comme argument, et qui, lorsqu'elle était appelée, ouvrait une connexion à la base de données, et à l'intérieur d'une déclaration d'utilisation, la transmettait à la fonction déléguée, pseudocode :

T RAIIWrapper<T>(Func<DbConnection, T> f){
  using (var db = new DbConnection()){
    return f(db);
  }
}

Ce n'est toujours pas aussi agréable ou évident que C++-RAII, mais cela permet d'obtenir à peu près la même chose. Chaque fois que nous avons besoin d'une DbConnection, nous devons appeler cette fonction d'aide qui garantit qu'elle sera fermée par la suite.

1 votes

Je suis un développeur .Net de formation. Pouvez-vous nous expliquer pourquoi l'utilisation de 'using' pose toujours problème (désordre et risque d'erreur) ? Est-ce que les ressources dans l'étendue 'using' ne sont pas instantanément éliminées après avoir quitté l'étendue mais attendent plutôt d'être collectées ? Si c'est le cas, dans quels cas cela sera-t-il source d'erreurs ?

20voto

Konrad Rudolph Points 231505

J'utilise le RAII C++ en permanence, mais j'ai également développé en Visual Basic 6 pendant longtemps, et le RAII y a toujours été un concept largement utilisé (bien que je n'aie jamais entendu personne l'appeler ainsi).

En fait, de nombreux programmes VB6 s'appuient assez largement sur RAII. L'une des utilisations les plus curieuses que j'ai vue à plusieurs reprises est la petite classe suivante :

' WaitCursor.cls '
Private m_OldCursor As MousePointerConstants

Public Sub Class_Inititialize()
    m_OldCursor = Screen.MousePointer
    Screen.MousePointer = vbHourGlass
End Sub

Public Sub Class_Terminate()
    Screen.MousePointer = m_OldCursor
End Sub

Utilisation :

Public Sub MyButton_Click()
    Dim WC As New WaitCursor

    ' … Time-consuming operation. '
End Sub

Une fois l'opération fastidieuse terminée, le curseur d'origine est restauré automatiquement.

14voto

wilhelmtell Points 25504

RAII signifie L'acquisition des ressources est l'initialisation . Ce n'est pas du tout un diagnostic de langage. Ce mantra est là parce que le C++ fonctionne comme il fonctionne. En C++, un objet n'est pas construit tant que son constructeur n'est pas terminé. Un destructeur ne sera pas invoqué si l'objet n'a pas été construit avec succès.

Traduit en langage pratique, un constructeur doit s'assurer qu'il couvre le cas où il ne peut pas accomplir son travail complètement. Si, par exemple, une exception se produit pendant la construction, le constructeur doit la gérer de manière élégante, car le destructeur ne sera pas là pour l'aider. Cela se fait généralement en couvrant les exceptions au sein du constructeur ou en transmettant ce problème à d'autres objets. Par exemple :

class OhMy {
public:
    OhMy() { p_ = new int[42];  jump(); } 
    ~OhMy() { delete[] p_; }

private:
    int* p_;

    void jump();
};

Si le jump() dans le constructeur jette nous avons des problèmes, parce que p_ va fuir. On peut réparer ça comme ça :

class Few {
public:
    Few() : v_(42) { jump(); } 
    ~Few();

private:
    std::vector<int> v_;

    void jump();
};

Si les gens ne sont pas conscients de cela, c'est pour deux raisons :

  • Ils ne connaissent pas bien C++. Dans ce cas, ils doivent ouvrir TCPPPL encore une fois avant d'écrire leur prochaine classe. Plus précisément, la section 14.4.1 de la troisième édition du livre parle de cette technique.
  • Ils ne connaissent pas du tout le C++. C'est bien. Cet idiome est très C++y. Soit vous apprenez le C++, soit vous oubliez tout cela et continuez votre vie. De préférence, apprenez le C++ ;)

11voto

Chris Jester-Young Points 102876

Pour les personnes qui commentent dans ce fil de discussion le RAII (resource acquisition is initialisation), voici un exemple motivant.

class StdioFile {
    FILE* file_;
    std::string mode_;

    static FILE* fcheck(FILE* stream) {
        if (!stream)
            throw std::runtime_error("Cannot open file");
        return stream;
    }

    FILE* fdup() const {
        int dupfd(dup(fileno(file_)));
        if (dupfd == -1)
            throw std::runtime_error("Cannot dup file descriptor");
        return fdopen(dupfd, mode_.c_str());
    }

public:
    StdioFile(char const* name, char const* mode)
        : file_(fcheck(fopen(name, mode))), mode_(mode)
    {
    }

    StdioFile(StdioFile const& rhs)
        : file_(fcheck(rhs.fdup())), mode_(rhs.mode_)
    {
    }

    ~StdioFile()
    {
        fclose(file_);
    }

    StdioFile& operator=(StdioFile const& rhs) {
        FILE* dupstr = fcheck(rhs.fdup());
        if (fclose(file_) == EOF) {
            fclose(dupstr); // XXX ignore failed close
            throw std::runtime_error("Cannot close stream");
        }
        file_ = dupstr;
        return *this;
    }

    int
    read(std::vector<char>& buffer)
    {
        int result(fread(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }

    int
    write(std::vector<char> const& buffer)
    {
        int result(fwrite(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }
};

int
main(int argc, char** argv)
{
    StdioFile file(argv[1], "r");
    std::vector<char> buffer(1024);
    while (int hasRead = file.read(buffer)) {
        // process hasRead bytes, then shift them off the buffer
    }
}

Ici, lorsqu'un StdioFile est créée, la ressource (un flux de fichiers, dans ce cas) est acquise ; lorsqu'elle est détruite, la ressource est libérée. Il n'y a pas de try ou finally bloc requis ; si la lecture provoque une exception, fclose est appelé automatiquement, car il se trouve dans le destructeur.

Il est garanti que le destructeur sera appelé lorsque la fonction quittera le système. main que ce soit en temps normal ou par exception. Dans ce cas, le flux de fichiers est nettoyé. Le monde est à nouveau en sécurité :-D

0 votes

Ajoutez du code pour montrer qu'il est utilisé. Expliquer pourquoi cela rend le code sûr pour les exceptions !

0 votes

Bon, voici mon premier essai ; dites-moi ce que vous en pensez :-)

29 votes

Je ne sais pas pourquoi cette réponse est votée. La question n'est pas de savoir ce qu'est le RAII, mais plutôt de connaître l'importance du concept pour les programmeurs qui ne sont pas en c++.

9voto

Loki Astari Points 116129

RAII.

Il commence par un constructeur et un destructeur, mais c'est bien plus que cela.
Il s'agit de contrôler en toute sécurité les ressources en présence d'exceptions.

La supériorité du RAII par rapport à d'autres mécanismes de ce type réside dans le fait qu'il rend l'utilisation du code plus sûre, car il transfère la responsabilité de l'utilisation correcte d'un objet de l'utilisateur de l'objet au concepteur de l'objet.

Lire la suite

Exemple à utiliser StdioFile correctement en utilisant le RAII.

void someFunc()
{
    StdioFile    file("Plop","r");

    // use file
}
// File closed automatically even if this function exits via an exception.

Pour obtenir la même fonctionnalité avec finalement.

void someFunc()
{
      // Assuming Java Like syntax;
    StdioFile     file = new StdioFile("Plop","r");
    try
    {
       // use file
    }
    finally
    {
       // close file.
       file.close(); // 
       // Using the finaliser is not enough as we can not garantee when
       // it will be called.
    }
}

Comme vous devez ajouter explicitement le bloc try{} finally{}, cette méthode de codage est plus sujette aux erreurs ( c'est-à-dire c'est l'utilisateur de l'objet qui doit penser aux exceptions). En utilisant RAII, la sécurité des exceptions doit être codée une seule fois lors de l'implémentation de l'objet.

A la question est ce C++ spécifique.
Réponse courte : Non.

Réponse plus longue :
Elle nécessite des constructeurs/destructeurs/exceptions et des objets qui ont une durée de vie définie.

Techniquement, il n'a pas besoin d'exceptions. Elle devient simplement beaucoup plus utile lorsque des exceptions peuvent être utilisées, car elle facilite le contrôle de la ressource en présence d'exceptions.
Mais elle est utile dans toutes les situations où le contrôle peut quitter une fonction prématurément et ne pas exécuter tout le code ( par exemple retour anticipé d'une fonction. C'est pourquoi les points de retour multiples en C sont une mauvaise odeur de code alors que les points de retour multiples en C++ ne sont pas une odeur de code [parce que nous pouvons nettoyer en utilisant RAII]).

En C++, la durée de vie contrôlée est obtenue par des variables de pile ou des pointeurs intelligents. Mais ce n'est pas le seul moyen d'avoir une durée de vie étroitement contrôlée. Par exemple, les objets Perl ne sont pas basés sur la pile mais ont une durée de vie très contrôlée grâce au comptage des références.

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