33 votes

Les programmeurs d'autres langages, outre C ++, utilisent-ils, connaissent-ils ou comprennent-ils RAII?

J'ai remarqué RAII a été d'obtenir beaucoup d'attention sur Stackoverflow, mais dans mes cercles (la plupart du temps C++) RAII est donc évident son comme de demander à ce qu'une classe ou un destructeur.

Donc, je suis vraiment curieux de savoir si c'est parce que je suis entouré de tous les jours, par hard-core les programmeurs en C++, et RAII n'est tout simplement pas bien connu en général (y compris C++), ou si tous ce questionnement sur Stackoverflow est dû au fait que je suis maintenant en contact avec des programmeurs qui n'ont pas grandi avec C++, et dans d'autres langues, les gens ne peuvent pas utiliser/connaître RAII?

24voto

jalf Points 142628

Il ya beaucoup de raisons pourquoi RAII n'est pas mieux connu. Tout d'abord, le nom n'est pas particulièrement évident. Si je n'avais pas déjà savoir ce que RAII a été, je serais certainement jamais le deviner à partir du nom. (L'acquisition de ressources est d'initialisation? Quel est le rapport avec le destructeur ou le nettoyage, qui est ce vraiment caractérise RAII?)

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

En C++, nous savons exactement quand le destructeur est appelé, nous savons que l'ordre dans lequel les destructeurs sont appelés, et que nous pouvons définir de faire quelque chose que nous aimons.

Dans la plupart des langues modernes, tout est garbage collector, ce qui rend RAII plus délicat à mettre en œuvre. Il n'y a aucune raison pourquoi il ne serait pas possible d'ajouter RAII-extensions à, disons, C#, mais il n'est pas aussi évident que c'est en C++. Mais comme d'autres l'ont mentionné, Perl et d'autres langues de soutien RAII en dépit d'être nettoyée.

Cela dit, il est toujours possible de créer votre propre RAII-style wrapper en C# ou en d'autres langues. Je l'ai fait en C# il y a un moment. J'ai dû écrire quelque chose pour s'assurer qu'une connexion de base de données a été fermé immédiatement après l'utilisation, une tâche qui tout programmeur C++ verrait comme un candidat évident pour le RAII. Bien sûr, nous pourrions tout envelopper dans using-états à chaque fois que nous avons utilisé une connexion db, mais ce n'est que désordre et sujette à erreur.

Ma solution a été d'écrire une fonction d'aide qui a eu un délégué comme argument, et puis quand on l'appelle, a ouvert une connexion de base de données, et à l'intérieur d'une aide de l'instruction, l'a transmis à la fonction de délégué, pseudo-code:

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

Toujours pas aussi beau ni évident que C++-RAII, mais il a atteint à peu près la même chose. Chaque fois que nous avons besoin d'un DbConnection, nous devons appeler cette fonction d'assistance qui garantit que ça va être fermée par la suite.

20voto

Konrad Rudolph Points 231505

J'utilise C++ RAII tout le temps, mais j'ai aussi développé en VB6 pour une longue période et RAII a toujours été un moyen largement utilisé concept là (bien que je n'ai jamais entendu quelqu'un l'appeler comme ça).

En fait, beaucoup de VB6 programmes reposent sur RAII assez fortement. Un des plus curieux de les utilisations que j'ai vu à plusieurs reprises est le petit de la classe:

' 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 le temps de l'opération se termine, le curseur d'origine est rétabli automatiquement.

14voto

wilhelmtell Points 25504

RAII signifie l'Acquisition de Ressources Est d'Initialisation. Ce n'est pas indépendant de la langue à tous. Ce mantra est ici parce que le C++ fonctionne de la façon dont il fonctionne. En C++, un objet n'est pas construit jusqu'à son constructeur se termine. Un destructeur ne sera pas invoquée si l'objet n'a pas été construit avec succès.

Traduit de pratique de la langue, un constructeur doit veiller à ce que des couvertures pour le cas où il ne peut pas terminer son travail à fond. Si, par exemple, une exception se produit lors de la construction, le constructeur doit la gérer correctement, parce que le destructeur ne sera pas là pour l'aider. Ceci est habituellement fait en couvrant pour les exceptions dans le constructeur ou par la transmission de cette dispute pour d'autres objets. Par exemple:

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

private:
    int* p_;

    void jump();
};

Si l' jump() appel dans le constructeur lève nous sommes en difficulté, parce qu' p_ des fuites. Nous pouvons résoudre ce comme ceci:

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

private:
    std::vector<int> v_;

    void jump();
};

Si les gens ne sont pas conscients de cela, alors c'est à cause de l'une des deux choses:

  • Ils ne savent pas C++ bien. Dans ce cas, ils devraient s'ouvrir TCPPPL de nouveau avant d'écrire leur prochain cours. Plus précisément, l'article 14.4.1 dans la troisième édition du livre parle de cette technique.
  • Ils ne savent pas C++ à tous. C'est très bien. Cet idiome est très C++y). Apprendre C++ ou d'oublier tout cela et continuer avec votre vie. De préférence apprendre le C++. ;)

11voto

Chris Jester-Young Points 102876

Pour les personnes qui sont l'insertion de commentaires dans ce fil de discussion au sujet de RAII (acquisition de ressources est d'initialisation), voici une motivation exemple.

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, quand un StdioFile instance est créée, la ressource (un fichier de flux, dans ce cas) est acquis; quand il est détruit, la ressource est libérée. Il n'y a pas d' try ou finally bloc requis; si la lecture provoque une exception, fclose est appelé automatiquement, parce que c'est dans le destructeur.

Le destructeur est garanti d'être appelé lorsque la fonction feuilles main, que ce soit normalement ou par exception à la règle. Dans ce cas, le flux de fichier est nettoyé. Le monde est sauvé une fois de plus. :-D

9voto

Loki Astari Points 116129

RAII.

Il commence avec un constructeur et un destructeur, mais il est plus que cela.
Il est tout au sujet en toute sécurité le contrôle des ressources en présence d'exceptions.


Ce qui fait RAII supérieure pour enfin et de ces mécanismes est que cela rend le code plus sûr à utiliser, car il se déplace de la responsabilité de l'utilisation d'un objet correctement à partir de l'utilisateur de l'objet, le concepteur de l'objet.

Lire ce

Exemple d'utilisation StdioFile correctement à l'aide de 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 enfin.

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.
    }
}

Parce que vous avez à ajouter explicitement le try{} finally{} bloquer ce qui rend cette méthode de codage plus enclins à faire des erreurs (c'est à dire qu'il est à l'utilisateur de l'objet qui doit penser à des exceptions). En utilisant RAII exception de sécurité doit être codée en une seule fois lorsque l'objet est mis en œuvre.

À la question est ce C++ spécifiques.
Réponse Courte: Non.


Plus De Réponse:
Il exige des Constructeurs/Destructeurs/Exceptions et les objets qui ont une durée de vie définie.

Eh bien, techniquement, il n'a pas besoin des exceptions. Il devient d'autant plus utile lorsque des exceptions pourraient potentiellement être utilisé, car il rend le contrôle de la ressource en présence d'exceptions très facile.
Mais il est utile dans toutes les situations où le contrôle peut quitter une fonction de début et de ne pas exécuter le code (par exemple, le retour précoce à partir d'une fonction. C'est pourquoi plusieurs points de retour dans C est une mauvaise odeur de code alors que plusieurs points de retour en C++ n'est pas une odeur de code [parce que nous pouvons nettoyer à l'aide du RAII]).

En C++ contrôlée durée de vie est obtenue par la pile des variables ou des pointeurs intelligents. Mais ce n'est pas le seul moment où nous pouvons avoir un contrôle étroit de la durée de vie. Par exemple Perl objets ne sont pas de pile mais ont un très contrôlée durée de vie en raison de comptage de référence.

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: