1
votes

C ++ comment déplacer un objet vers un nullptr

Je pense à un cas d'utilisation étrange où je veux déplacer un objet vers un nullptr. Peut-être devrais-je donner un fragment de code:

class Objpair {
   public:
      Objpair(Obj&& a, Obj&&b) : first(&a), second(&b) { }
   private:
       Obj* first;
       Obj* second;
};

Le problème est que lorsque a et b sont hors de portée, le premier et le deuxième pointeur seront suspendus. Si je peux déplacer Object a sur le premier pointeur, il ne devrait y avoir aucun problème de problèmes de double libre et de portée. Si le membre était d'abord déclaré comme pointeur Obj et non Obj *, alors le premier droit (std :: move (a)) ferait le travail. Je dois faire quelque chose de mal ici. Je pense déplacer au lieu de copier car j'essaye de transférer le contrôle d'un autre objet vers l'objet courant et d'améliorer les performances.

La version pointeur est utilisée car je pense au comportement polymorphe du membre objet.


7 commentaires

Si le membre était d'abord déclaré comme pointeur Obj et non Obj * , alors le premier droit (std :: move (a)) ferait l'affaire. Pourquoi n'êtes-vous pas D'après cela?


Quel est le sens de stocker les adresses des expressions rvalue? a et b sont des objets qui sont probablement sur le point de mourir. Qu'essayez-vous d'accomplir?


le premier et le second peuvent pointer vers la classe de base, dérivé 1, dérivé2, ...


"se déplacer vers un pointeur non initialisé" n'a aucun sens pour moi. Vous devez avoir un Obj réel pour y accéder. "quand a et b sont hors de portée, le premier et le deuxième pointeur seront en suspension" Pas quand a et b sont détruits. Lorsque les objets que vous avez utilisés pour les initialiser sont détruits.


Le fragment est erroné, ne le précisant pas d'une manière ou d'une autre. Comme mentionné, ne devrait pas utiliser cela. Vous recherchez un meilleur design.


Dans un environnement multi-thread, une partie du code crée des objets; une autre partie du code les récupère et les compose dans un type différent. Je pourrais faire avec la version pointeur. Je cherche juste la possibilité d'utiliser la référence d'objet et de convertir des pointeurs. Une telle syntaxe est-elle même possible en C ++.


@KeminZhou, il est assez difficile de dire ce que vous essayez d'accomplir. Je crains qu'il ne s'agisse d'un problème X / Y ou que vous essayez de faire une optimisation prématurée. Pourriez-vous expliquer le problème global que vous essayez de résoudre et pourquoi cela vous a amené à essayer cette conception?


4 Réponses :


2
votes

Vous ne pouvez pas déplacer des objets sur des pointeurs (nullptr ou autre). Vous ne pouvez pas non plus prolonger la durée de vie des objets en faisant pointer des pointeurs vers eux - C ++ ne fonctionne tout simplement pas de cette façon.

Puisque vous avez besoin d'un comportement polymorphe, vous ne pouvez pas simplement copier-construire ou affecter un Obj code> pointeur. Vous voudrez probablement quelque chose comme une méthode clone () pour la classe Obj , comme indiqué dans cette question:

Copie d'entités dérivées en utilisant uniquement des pointeurs de classe de base, (sans test exhaustif!) - C ++

pour en savoir plus. Souvenez-vous cependant que vous ne pouvez pas prolonger la durée de vie de la référence rvalue, vous devez donc construire un nouvel objet d'une manière ou d'une autre. N'oubliez pas également d'éviter les fuites de mémoire. Donc, si votre clone () produit un Obj * , vous devrez écrire quelque chose comme:

template <typename T1, typename T2>
class Objpair {
   public:
      Objpair(T1&& a, T2&&b) : first(a), second(b) 
      { }
   private:
      T1 first;
      T2 second;
};

sur peut-être std :: shared_ptr au lieu de les unique_ptr (voir cette réponse sur le choix entre les deux).

Si vous avez besoin d'un comportement "polymorphe", mais que vous connaissez le types exacts de a et b au moment de la compilation, et cela ne vous dérange pas d'avoir différentes classes ObjPair , vous pouvez également essayer quelque chose comme ceci:

class Objpair {
   public:
      Objpair(Obj&& a, Obj&&b) : 
         first(a.clone()), 
         second(b.clone()) 
      { }
   private:
      std::unique_ptr<Obj> first;
      std::unique_ptr<Obj> second;
};

auquel cas aucun pointeur n'est impliqué (!)


0 commentaires

2
votes

Dans un commentaire, vous avez dit

Le premier et le second peuvent pointer vers BaseClass, Derived1, Derived2,

Dans ce cas, votre meilleure option est de fournir une fonction membre virtual dans Obj pour cloner un objet.

 class Objpair {
   public:
      Objpair(Obj&& a, Obj&&b) : first(a.clone()), second(b.clone()) { }
   private:
       Obj* first;
       Obj* second;
};

et utilisez-le pour initialiser d'abord et second.

class Obj
{
   public:
      virtual Obj* clone() const = 0;
   ...
}

et assurez-vous que vous prendre soin de désallouer la mémoire pointée par premier et deuxième dans le destructeur. Voir La règle de trois pour plus de détails.

Vous pouvez utiliser des pointeurs intelligents pour éliminer le besoin de code dans votre classe pour gérer la durée de vie des objets. Que vous utilisiez std :: unique_ptr ou std :: shared_ptr dépend de la façon dont vous souhaitez utiliser Objpair.


0 commentaires

5
votes

Ce que vous pouvez faire est de déplacer les objets de construction de vos paramètres comme ceci:

class Objpair {
   public:
      Objpair(Obj&& a, Obj&& b)
      : first(std::make_unique<Obj>(std::move(a)))
      , second(std::make_unique<Obj>(std::move(b)))
      {}

   private:
       std::unique_ptr<Obj> first;
       std::unique_ptr<Obj> second;
};

Je vous recommande d'utiliser std :: unique_ptr :

class Objpair {
   public:
      Objpair(Obj&& a, Obj&& b)
      : first(new Obj(std::move(a)))
      , second(new Obj(std::move(b)))
      {}

   private:
       Obj* first;
       Obj* second;
};


4 commentaires

La construction sera une construction move sans avoir besoin de std :: move () , puisque le ctor obtient des références rvalue pour commencer.


@einpoklum Non, vous devez std :: move car une fois que les références-valeurs-r sont liées, elles deviennent des références-valeurs-l . Vous devez soit std :: move soit std :: forward (pour transférer les références) à chaque étape.


unique ou shared_ptr sont de bons choix. Le pointeur nu est toujours plus difficile à suivre.


Les références @Galik rvalue ne «deviennent pas des références lvalue» ici. Mais vous avez raison de dire que std :: move est nécessaire. Elle est nécessaire car l'expression id a est une expression lvalue - quel que soit le type de l'expression qui est la référence rvalue.



0
votes

La réponse acceptée par @ Galik est bonne. Bien que cela nécessite que Obj soit au moins déplaçable, ce qui est similaire à d'autres réponses avec les solutions Clone () - elles mettent toutes certaines exigences sur Obj et ses descendants. Pour supprimer cette restriction, nous pouvons changer le constructeur de Objpair pour accepter std::unique_ptr:

#include <memory>

class Obj
{
public:
    Obj() {}

    // non-copyable/non-movable
    Obj(const Obj&) = delete;
    Obj& operator=(const Obj&) = delete;
};

class Objpair {
   public:
      Objpair(std::unique_ptr<Obj>&& a, std::unique_ptr<Obj>&& b)
      : first(std::move(a))
      , second(std::move(b))
      {}

   private:
       std::unique_ptr<Obj> first;
       std::unique_ptr<Obj> second;
};

int main()
{
    Objpair p{std::make_unique<Obj>(), std::make_unique<Obj>()};
    return 0;
}

Les bonus possibles sont:

  • l'utilisateur s'occupe de l'allocation de mémoire et a donc un meilleur contrôle sur celle-ci.
  • l'utilisateur peut utiliser des instances des paramètres a et b avant de les transmettre à Objpair , de manière polymorphe.


0 commentaires