2
votes

Quel est le meilleur type de pointeur / référence lors du retour d'un membre éventuellement alloué sur la pile en C ++?

Donc pour illustrer ma question, j'ai fait un exemple:

#include <iostream>

using namespace std;


struct A
{
  void doSomething (){
      cout << "Something\n";
  }
};


struct B
{
  A a;

  A *getA ()
  {
    return &a;
  }
};


int
main ()
{

  B *b = new B ();
  A *a = b->getA ();


  // POINT 1
  if (nullptr != a)
    {
      a->doSomething ();
    }

  delete b;
  b = nullptr;

  // POINT 2
  if (nullptr != a)
    {
      a->doSomething ();
    }

  return 0;
}

Cela compile et s'exécute sans erreur sur ma machine, mais si vous inspectez le code, il y a vraiment un problème de balançoire pointeur sur les lignes suivant le commentaire marqué "POINT 2".

Puisque b a été supprimé, alors a est désormais invalide (puisqu'il a été supprimé par dtor de b).

Je pourrais donc utiliser un pointeur partagé pour remédier à cela, mais cela garderait l'instance de a même après b a été supprimé, et je ne pourrais pas non plus allouer a sur la pile. Ce sont deux choses que je veux éviter. Au lieu de cela, je veux simplement savoir si a est toujours valide.

J'aurais pu aussi utiliser un pointeur unique mais alors je ne pourrais avoir qu'une seule instance de a code> ce qui n'est pas non plus ce que je veux, je veux de nombreuses copies du pointeur vers a.

Il y a donc un type de pointeur / référence existant qui me permettrait de le faire ? Y a-t-il une raison pour laquelle c'est une bonne / mauvaise idée?


4 commentaires

N'utilisez pas du tout de pointeurs bruts. Utilisez plutôt des pointeurs intelligents pour clarifier la propriété des instances.


Il n'y a pas de solution simple et rapide. Si vous voulez connaître l'état de validité d'un a , vous devez conserver ces informations quelque part . Un objet ne peut pas vous informer de l'état actuel de sa durée de vie, il devrait être vivant pour l'interroger sur son état actuel.


"Je pourrais utiliser un pointeur partagé pour remédier à cela, mais cela garderait l'instance de a autour même après la suppression de b" Pas si getA () renvoyait un code low_ptr .


Si vous souhaitez que a partage la propriété des a de b , vous avez besoin d'un shared_ptr sinon vous pouvez utiliser un low_ptr


3 Réponses :


3
votes

Vous venez de découvrir les merveilles de la sémantique de propriété :)

La façon de résoudre ce problème dépend de la conception de votre application: ce dont vous avez besoin et ce que vous essayez d'accomplir.

Dans ce cas, si vous souhaitez vraiment partager la propriété d'un objet, utilisez < code> std :: shared_ptr qui garde un compte de référence du nombre de pointeurs restants, de sorte que le dernier supprime l'objet; éventuellement std :: low_ptr si vous avez seulement besoin pour vérifier si l'objet est toujours vivant mais ne veut pas le garder en vie plus longtemps que nécessaire.

Cependant, notez que (ab) l'utilisation de pointeurs partagés peut être le signe d'une mauvaise conception.


Au fait, votre membre A a; n'est pas alloué dans la pile (c'est-à-dire que le titre est faux).


5 commentaires

Oui, je souhaite utiliser RIIA, mais je peux toujours accéder en toute sécurité aux membres en dehors de la classe. C'est peut-être une mauvaise conception: /


@LennartRolland Dans ce cas, placez simplement le membre dans un std :: shared_ptr . Si vous voulez vraiment éviter l'allocation / l'indirection, procédez comme le suggère @Slava. Mais oui, je reconsidérerais sérieusement le design. La plupart des problèmes peuvent être résolus sans compter sur la propriété partagée.


Vous avez raison de dire que A n'est pas alloué sur la pile ici - il est alloué là où B est alloué, non? il pourrait donc être alloué dans la pile mais pas dans cet exemple.


@LennartRolland En effet, ce a est membre de B , donc partout où vous allouez une instance de B , c'est là que le membre sera. Dans ce cas, puisque vous avez écrit new B , ce serait le magasin gratuit (le tas).


Oubliez pile et tas. Parlez plutôt de la durée de stockage.



3
votes

La seule solution viable utilisant la bibliothèque standard qui vient à l'esprit est d'utiliser std :: low_ptr () - cela permettra de vérifier la validité de l'objet sans en détenir la propriété. Cela vient avec le prix - vous devez en conserver la propriété avec std :: shared_ptr . Bien qu'il soit possible de créer std :: shared_ptr sur un objet avec une durée de stockage automatique et un suppresseur de noop, je ne le ferais que si j'en ai vraiment besoin, car une telle méthode est sujette aux erreurs et va à l'encontre du but d'un smart pointeur.


3 commentaires

@Acorn Je comprends, j'ai mentionné que pour le souci d'OP que l'utilisation de std :: shared_ptr éliminera la possibilité de créer cet objet avec une durée de stockage automatique (c'est-à-dire sur la pile).


Ouais, je l'ai compris trop tard (supprimé le commentaire), désolé.


@Acorn rien à redire



1
votes

Le meilleur moyen est de ne pas exposer a .

Votre B est l'interface. Donnez-lui les fonctions dont vous avez besoin. Demandez-lui de continuer à invoquer tout ce dont il a besoin pour invoquer le a pour que cela se produise.

Ensuite, supprimez getA () et rendez un privé.

Maintenant, il est complètement encapsulé et la portée appelante ne peut pas jouer avec ça comme ça!

Pas besoin de pointeurs ou d'allocation dynamique; juste une bonne POO à l'ancienne.


0 commentaires