Question

S'il vous plaît suppose que j'ai une fonction qui accepte un pointeur comme paramètre. Cette fonction peut lancer une exception, car il std::vector<>::push_back() utilise pour gérer le cycle de vie de ce pointeur. Si je le déclare comme ceci:

void manage(T *ptr);

et l'appeler comme ceci:

manage(new T());

si elle jette une exception en poussant le pointeur dans la std::vector<>, j'ai effectivement eu une fuite de mémoire, non?

Would déclarant la fonction comme ceci:

void manage(std::auto_ptr<T> ptr);

résoudre mon problème?

J'attendre d'abord affecter la std::auto_ptr sur la pile (quelque chose que je pense ne pourrait jamais lancer une exception) et le laisser acquérir la propriété sur le pointeur. Safe.

Ensuite, à l'intérieur de la fonction, je pousserais le pointeur brut dans le std::vector<>, qui est aussi sûre: si cela échoue, le pointeur ne sera pas ajouté, mais le pointeur intelligent sera tout de même posséder le pointeur de sorte qu'il sera détruit . Si la poussée réussit, je supprimerais la propriété de pointeur intelligent sur ce pointeur et retour:. Cela ne peut pas lancer une exception, il sera toujours bien

sont mes théories correctes?

- Edition -

Non, je pense que je ne peux pas faire cela. En faisant cela, il faudrait prendre une référence non-const à rvalue (pour enlever la propriété du pointeur intelligent). Je dois écrire

std::auto_ptr<T> ptr(new T());
manage(ptr);

pour que travailler, quelque chose qui est tout à fait inconvient dans mon cas. J'écris ceci pour que je puisse mettre en œuvre RAII sans polluer beaucoup le code. Faire cette chose ne serait pas utile, alors. Ce serait prise 22.

- Edition 2 -

Tirer ce que Jason Orendorff dit là-bas pour référence rapide par les lecteurs, la solution ultime semble être le suivant:

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

Cela résout le problème de l'inutile référence non const à rvalue.

Quand je finis cette classe je codage je vais le poster de retour ici au cas où quelqu'un juge utile.

- Edition 3 -

Ok, il y a eu beaucoup de discussions ici, et il y a des points clés que j'aurais clarifiées avant. En général, quand je poste à StackOverflow, je tente d'expliquer la raison de mes questions et, en général, qui est tout à fait inutile. Donc, cette fois, je pensais que je devrais aller droit au but. Il s'avère que cela ne fonctionne pas assez bien XD

Malheureusement, mon cerveau est supercondamnation maintenant, donc je pense que je ne vais même pas être en mesure d'expliquer correctement ce que je pensais d'abord à atteindre mes objectifs. Je suis en train de trouver une bonne solution pour l'écriture des opérations atomiques et de code d'exception de sécurité qui correspond à un bon nombre de cas, mais vraiment, je ne peux pas le supporter XD Je pense que c'est le genre de chose que je ne maîtrise avec le temps.

Je suis vraiment nouveau programmeur C ++ et mon objectif est le développement de jeux. Quand une exception est levée dans un moteur de jeu, il est la fin de l'exécution. Le système libérera toute la mémoire de mon processus, il ne compte pas vraiment si un ou deux pointeurs de fuite ici et là. Maintenant que je développe une application serveur, je trouve qu'il est difficile de traiter avec des exceptions, parce qu'une exception ne peut pas planter le serveur; il doit « planter la demande ».

C'est: « Eh bien, le client, malheureusement, les développeurs ne pas prévoir cette condition, vous devrez donc essayer plus tard (jusqu'à ici, il est fondamentalement la même chose que d'un moteur de jeu, rien n'est réparée, seulement il est isolé dans le contexte de seulement une demande, et non l'ensemble du processus). Mais ne paniquez pas, comme tout a été laissé dans un état valide (ici est l'une des différences, . cependant, le processus ne se termine pas, de sorte que le système d'exploitation ne peut pas libérer les ressources pour vous, en outre, vous devez faire attention à défaire les opérations à ce jour, de sorte que vous ne bloquez pas complètement le compte d'un utilisateur, par exemple , ou même un service complet fourni par le serveur). ".

Je vais coder de plus en plus et notez mes problèmes afin que je puisse écrire une meilleure question la prochaine fois. Je n'étais pas prêt àdemandez maintenant, je suis vraiment désolé.

Merci beaucoup pour vos réponses, je aime vraiment stackoverflow. Il est absolument incroyable à quelle vitesse mes questions sont répondues et comment éclairer vos réponses. Merci.

Était-ce utile?

La solution

Je pense que la discussion a été assez généré pour justifier une autre réponse.

Tout d'abord, pour répondre à la question réelle, oui, il est tout à fait approprié (et même nécessaire!) Pour passer un argument par pointeur intelligent lorsque le transfert de propriété a lieu. En passant par un pointeur intelligent est un idiome commun pour y parvenir.

void manage(std::auto_ptr<T> t) {
    ...
}
...

// The reader of this code clearly sees ownership transfer.
std::auto_ptr<T> t(new T);
manage(t);

Maintenant, il y a une très bonne raison pour laquelle tous les pointeurs intelligents ont des constructeurs explicites. Considérons la fonction suivante (remplacer mentalement std::auto_ptr avec boost::shared_ptr si elle chatouille votre fantaisie):

void func(std::auto_ptr<Widget> w, const Gizmo& g) {
    ...
}

Si std::auto_ptr avait un constructeur implicite, tout à coup ce code compilerait:

func(new Widget(), gizmo);

Qu'est-ce qui ne va pas avec elle? La prise presque directement de "efficace C ++", Point 17:

func(new Widget(), a_function_that_throws());

Parce que dans C ++ ordre d'évaluation des arguments est défini, vous pouvez très bien avoir des arguments évalués dans l'ordre suivant: new Widget(), a_function_that_throws(), constructeur de std::auto_ptr. Si une fonction renvoie, vous avez une fuite.

Par conséquent, toutes les ressources qui seront libérés besoin à emballer sur la construction dans les classes RAII avant être passé dans une fonction. Cela signifie que tout pointeur intelligent doivent être construits avant de les transmettre comme argument à une fonction. Pointeurs intelligents font-être avec une copie constructible référence const ou implicitement encouragerait le code à bâtir dangereux. Construction explicite applique un code plus sûr.

Maintenant, pourquoi devriez-vous pas faire quelque chose comme ça?

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

Comme déjà mentionné, les idiomes d'interface me disent que je peux passer un pointeur que je possède et je reçois de le supprimer. Donc, rien ne me empêche de faire ceci:

T item;
manage(&t);

// or
manage(&t_class_member);

Ce qui est désastreux, bien sûr. Mais vous dites « bien sûr, je sais ce que les moyens d'interface, je ne l'utilise de cette façon ». Cependant, vous pouvez ajouter un argument supplémentaire pour une fonction plus tard. Ou quelqu'un (pas vous, ou vous 3 ans plus tard) vient ce code, ils voient pas de la même façon que vous le faites.

  1. Cette hypothétique « quelqu'un d'autre » ne peut voir un en-tête sans commentaire et (à juste titre) présumer l'absence de transfert de propriété.
  2. Ils peuvent voir comment cette fonction est utilisée dans un autre code et reproduire l'utilisation sans regarder l'en-tête.
  3. Ils peuvent utiliser l'auto-complétion de code pour appeler une fonction et ne pas lire le commentaire ou la fonction et présumer l'absence de transfert de propriété.
  4. Ils peuvent écrire une fonction qui vous enveloppe la fonction manage et encore quelqu'un d'autre utilisera la fonction enveloppe et manquera la documentation de la fonction originale.
  5. Ils peuvent essayer de "prolonger" code de sorte que tous les anciens compiles de code (et devient automatiquement dangereux):

    void manage(T *t, const std::string tag = some_function_that_throws());
    

Comme vous pouvez le voir, la construction explicite d'un pointeur intelligent rend beaucoup plus difficile d'écrire du code dangereux dans les cas ci-dessus.

Par conséquent, je ne recommanderais pas aller à l'encontre des décennies d'expertise de C pour faire perceivably API « plus agréable » et « fun ».

Mon 2c.

Autres conseils

Vous pouvez le faire de cette façon, mais vous avez encore le nettoyage en cas d'exception ne sont pas jetés, ce qui semble un peu onéreux.

Si vous utilisez quelque chose comme boost :: shared_ptr (je crois quelque chose comme ceci est dans les bibliothèques TR1 ainsi - comme un exemple, voir la mise en œuvre de MS ) vous pouvez oublier d'avoir à le nettoyage en cas de choses qui se passent comme prévu.

Pour faire ce travail, vous devrez faire votre vecteur accepter par exemple boost::shared_ptr < T >, alors vous simplement laisser nettoyer votre instance d'origine et elle laisserait par exemple dans le vecteur vivant dans le cas où tout va bien. Dans le cas où les choses vont mal toutes les instances de boost::shared_ptr seraient détruits et vous finiriez encore avec aucune fuite.

Avec des pointeurs intelligents, il est à peu près la cueillette qui convient à la tâche, et dans ce cas la propriété partagée (ou un simple transfert de propriété) semble être un objectif donc il y a de meilleurs candidats que std :: auto_ptr sur la plupart des plates-formes.

En général, en utilisant std::auto_ptr comme un type d'argument de fonction indique sans ambiguïté l'appelant qui fonctionnent prendra la propriété de l'objet, et sera responsable de le supprimer. Si votre fonction correspond à cette description, puis par tous les moyens y utiliser auto_ptr indépendamment de toute autre raison.

Oui, c'est très bien. (Voir ci-dessous modifier.) (jkp peut être voir quelque chose que j'ai manqué, mais je ne pense pas que « ils doivent encore nettoyer en cas d'une exception levée » parce que, comme vous le dites, dans ce cas, le auto_ptr supprimera l'objet pour vous.)

Mais je pense qu'il serait mieux de cacher les manigances de auto_ptr de l'appelant:

void manage(T *t) {
    std::auto_ptr<T> p(t);  // to delete t in case push_back throws
    vec.push_back(t);
    p.release();
}

EDIT: J'ai écrit d'abord: « Oui, c'est très bien, » se référant au plan de manage(auto_ptr<T>) original, mais je l'ai essayé et a constaté que cela ne fonctionne pas. Le constructeur auto_ptr<T>::auto_ptr(T *) est explicit. Le compilateur ne vous laissera pas écrire manage(new T) car il ne peut pas convertir implicitement que pointeur sur un auto_ptr. manage(T *) est une interface plus conviviale de toute façon!

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