12
votes

Apprendre C ++: Renvoyer des références et se déplacer

Je vais avoir un diable de temps compréhensif. Considérez le code suivant:

class Animal
{
public:
    virtual void makeSound() {cout << "rawr" << endl;}
};

class Dog : public Animal
{
public:
    virtual void makeSound() {cout << "bark" << endl;}
};

Animal* pFunc()
{
    return new Dog();
}

Animal& rFunc()
{
    return *(new Dog());
}

Animal vFunc()
{
    return Dog();
}

int main()
{
    Animal* p = pFunc();
    p->makeSound();

    Animal& r1 = rFunc();
    r1.makeSound();

    Animal r2 = rFunc();
    r2.makeSound();

    Animal v = vFunc();
    v.makeSound();
}


2 commentaires

Les références Java ressemblent davantage à des pointeurs C ++. Java n'a rien comme les références C ++.


@ Alex's Suggestion est une bonne. "Efficace C ++" et "plus efficace C ++", à la fois par Scott Meyers, sont d'excellents livres et ont immédiatement contribué à ma compréhension de C ++, en particulier avec des problèmes difficiles comme la vôtre. Bien sûr, j'ai oublié la plupart d'entre eux depuis que je n'ai pas fait sérieux C ++ depuis quelques années :(


8 Réponses :


6
votes

1) Si vous créez de nouveaux objets, vous ne voulez jamais retourner une référence (voir votre propre commentaire sur # 3.) Vous pouvez retourner un pointeur (éventuellement enveloppé par std :: Shared_ptr ou std :: auto_ptr ). (Vous pouvez également revenir par copie, mais cela est incompatible avec l'utilisation de l'opérateur nouveau ; il est également légèrement incompatible avec le polymorphisme.)

2) rfunc est juste faux. Ne fais pas ça. Si vous avez utilisé Nouveau pour créer l'objet, retournez-la via un pointeur (éventuellement enveloppé).

3) Vous n'êtes pas censé. C'est ce que font les pointeurs.


edit (répondant à votre mise à jour :) Il est difficile d'imaginer le scénario que vous décrivez. Serait-il plus précis de dire que le pointeur retourné peut être invalide une fois que l'appelant a appelé à une autre méthode (spécifique)?

Je conseillerais contre l'utilisation d'un tel modèle, mais si vous devez absolument faire cela, et vous devez le respecter dans votre API, vous devez probablement ajouter un niveau d'indirection, voire deux. Exemple: enveloppez l'objet réel dans un objet compté de référence qui contient le point réel. Le pointeur d'objet compté par référence est défini sur null lorsque l'objet réel est supprimé. Ceci est laid. (Il peut y avoir de meilleurs moyens de le faire, mais ils peuvent toujours être laids.)


2 commentaires

Pointeur ftw. Ils ne se convertissent pas implicitement en une valeur, votre appelant est donc averti du polymorphisme potentiel.


Vous pourriez être intéressé par ma nouvelle question: Stackoverflow.com/questions/4410790/...



1
votes

Pour éviter de trancher, vous devez retourner ou transmettre un pointeur em> à l'objet. (Notez qu'une référence est fondamentalement un "pointeur perméférencé de permanence".

Animal& rFunc()
{
    return *(new Dog());
}


2 commentaires

Vous pouvez toujours séparer avec un pointeur: animal * Panimal = * chien; provoquera la division.


Alors cette réponse est-elle trompeuse? Stackoverflow.com/a/3835757/1538531 Parce que lorsque vous devez retourner polymorphiquement, vous ne pouvez pas vous contourner de retourner un pointeur .



0
votes

(J'ignore vos problèmes avec une mémoire dynamique allant dans des références provoquant des fuites de mémoire ...)

Vos problèmes de fractionnement disparaissent lorsque l'animal est une classe de base abstraite. Cela signifie qu'il a au moins une méthode virtuelle pure et ne peut pas être directement instancié. Ce qui suit devient une erreur de compilateur: p> xxx pré>

mais le compilateur permet: p>

Animal* a = pFunc();  // polymorphism maintained!
Animal& a = rFunc();  // polymorphism maintained!


3 commentaires

Euh non. Dans le cas de animal & a = rfunc () , comment supprimez-vous l'objet lorsque vous en avez terminé?


@Dan je sais que, le problème Splitting le problème disparaît ... pas la fuite de mémoire :)


Ok, mais n'encourage pas les débutants ;-)



0
votes

Point 1: N'utilisez pas de références. Utilisez des pointeurs.

Point 2: La chose que vous avez ci-dessus est appelée taxonomie qui est un système de classification hiérarchique. Les taxonomies sont l'exemple d'une sorte qui est totalement inappropriée pour la modélisation orientée objet. Votre exemple trivial ne fonctionne que parce que votre animal de base suppose que tous les animaux font un bruit et ne peuvent rien faire d'autre intéressant.

Si vous essayez de mettre en œuvre une relation, telle que

Virtual Bool Animal :: mange (animal * autre) = 0;

Vous trouverez que vous ne pouvez pas le faire. La chose est: le chien n'est pas un sous-type d'abstraction animale. L'ensemble des taxonomies est que les classes de chaque niveau de la partition ont de nouvelles propriétés intéressantes.

Par exemple: Les vertébrés ont une colonne vertébrale et nous pouvons demander s'il est fait de cartilédeur ou d'os. Nous ne pouvons même pas poser cette question d'invertébrés.

Pour bien comprendre, vous devez voir que vous ne pouvez pas faire un objet de chien. Après tout, c'est une abstraction, non? Parce que, il y a des kilistroyaux et des collies, et un chien individuel doit être de certaines espèces. Le schéma de classification peut être aussi profond que vous le souhaitez, mais il ne peut jamais supporter d'individus concrets. Fido est pas-a-chien , c'est juste son étiquette de classification.


0 commentaires

2
votes

Pour répondre à la deuxième partie de votre question ("Comment puis-je communiquer que le pointeur est soumis à une suppression à tout moment") -

Ceci est une pratique dangereuse et a des détails subtils que vous devrez envisager. C'est racé dans la nature. P>

Si le pointeur peut être supprimé à tout moment, il n'est jamais prudent de l'utiliser dans un autre contexte, car même si vous vérifiez "êtes-vous toujours valide?" à chaque fois, il peut être supprimé juste un peu un peu après le chèque, mais avant de l'utiliser. P>

Un moyen sûr de faire ces choses est le concept "faible pointeur" - avoir l'objet être stocké En tant que pointeur partagé (un niveau d'indirection, peut être libéré à tout moment) et avoir la valeur renvoyée être un pointeur faible - quelque chose que vous devez question em> avant de pouvoir utiliser et doit relâcher après vous 'j'ai utilisé. De cette façon, l'objet est toujours valide, vous pouvez l'utiliser. P>

Code pseudo (basé sur des pointeurs faibles et partagés inventés, je n'utilise pas de boost ...) - P>

weak< Animal > animalWeak = getAnimalThatMayDisappear();
// ...
{
    shared< Animal > animal = animalWeak.getShared();
    if ( animal )
    {
        // 'animal' is still valid, use it.
        // ...
    }
    else
    {
        // 'animal' is not valid, can't use it. It points to NULL.
        // Now what?
    }
}
// And at this point the shared pointer of 'animal' is implicitly released.


1 commentaires

Il est important de noter que vous ne pouvez pas utiliser directement sur le pointeur faible, mais devez créer un pointeur partagé pour assurer la durée de vie de l'objet sous-jacent. L'utilisation directe du pointeur faible aurait un comportement indéfini (l'objet pourrait être supprimé après le test et avant la fin de l'opération).



1
votes

Si je repasse un pointeur Comment puis-je communiquer au programmeur que le pointeur n'est pas le leur à supprimer si tel est le cas? Ou bien comment puis-je communiquer que le pointeur est soumis à la suppression à tout moment (du même thread mais une fonction différente) de sorte que la fonction d'appel ne doit pas le stocker, si c'est le cas.

Si vous ne pouvez vraiment pas faire confiance à l'utilisateur, ne leur donnez pas de pointeur: transmettez une poignée de type entier et exposez une interface de style C (par exemple, vous avez un vecteur d'instances de votre côté de la clôture, et vous exposez une fonction qui prend l'entier comme le premier paramètre, indexe dans le vecteur et appelle une fonction de membre). C'est la voie à l'ancienne (malgré que nous n'avions pas toujours des choses fantaisistes comme des "fonctions membres";)).

Sinon, essayez d'utiliser un pointeur intelligent avec une sémantique appropriée. Personne sans penser que Supprimer & * quelque_boost_shared_ptr; est une bonne idée.


0 commentaires

1
votes

Mais disons-vous que je veux une fonction qui retourne une valeur animale qui est vraiment un chien. p>

  1. Puis-je comprendre correctement que le plus proche que je puisse obtenir est une référence? Li> ol>

    Oui, vous êtes correct. Mais je pense que le problème n'est pas tellement que vous ne comprenez pas les références, mais que vous ne comprenez pas les différents types de variables en C ++ ou comment nouveau code> fonctionne en C ++. En C ++, les variables peuvent être une donnée primitive (int, flotteur, double, etc.), un objet, un pointeur / une référence à une primitive et / ou d'objet. En Java, les variables ne peuvent être qu'une primitive ou une référence à un objet. P>

    en C ++, lorsque vous déclarez une variable, la mémoire réelle est attribuée et associée à la variable. En Java, vous devez créer explicitement des objets en utilisant de nouvelles et d'attribuer explicitement le nouvel objet à une variable. Le point clé ici est que c'est que, en C ++, l'objet et la variable que vous utilisez pour accéder ne sont pas la même chose lorsque la variable est un pointeur ou une référence. animal a; code> signifie quelque chose de différent de animal * A; code> ce qui signifie quelque chose de différent de animal & a; code>. Aucun de ceux-ci n'a de types compatibles, et ils ne sont pas interchangeables. P>

    Lorsque vous tapez, animal A1 code> en C ++. Un nouvel objet code> animal code> est créé. Donc, lorsque vous tapez animal A2 = A1; code>, vous vous retrouvez avec deux variables ( A1 code> et A2 code>) et deux animal Objets à un emplacement différent en mémoire. Les deux objets ont la même valeur, mais vous pouvez modifier leurs valeurs indépendamment si vous le souhaitez. En Java, si vous avez tapé le même code exact, vous vous retrouveriez avec deux variables, mais un seul objet. Tant que vous ne réagiez ni des variables, ils auraient toujours la même valeur. P>

    1. En outre, est-il titulaire de celui utilisant l'interface RFunC pour voir que la référence renvoyée est attribuée à un animal et? (Ou configurez-vous de manière intentionnelle la référence à un animal qui, via la tranchée, rejette le polymorphisme.) Li> ol> blockQuote>

      Lorsque vous utilisez des références et des pointeurs, vous pouvez accéder à une valeur d'un objet sans la copier sur l'endroit où vous souhaitez l'utiliser. Cela vous permet de le changer de l'extérieur des accolades frisées où vous avez déclaré l'objet dans l'existence. Les références sont généralement utilisées comme paramètres de fonction ou pour renvoyer les membres de données privés d'un objet sans en faire une nouvelle copie. En règle générale, lorsque vous recevez une référence, vous ne l'attribuez à rien. Utilisation de votre exemple, au lieu d'attribuer la référence renvoyée par rfunc () code> à une variable, on saisit normalement rfunc (). Cadeau (); p> p> Donc, oui, il incombe à l'utilisateur de rfunc () code>, s'ils attribuent la valeur de retour à quoi que ce soit, pour l'attribuer à une référence. Vous pouvez voir pourquoi. Si vous attribuez la référence renvoyée par rfunc () code> à une variable déclarée comme animal animal_variable code>, vous vous retrouvez avec une variable code> animal code>, un Animal code> objet, et un chien code>. L'objet code> animal code> associé à animal_variable code> est, autant que possible, une copie de l'objet code> chien code> renvoyé par référence à partir de rfunc () code>. Mais, vous ne pouvez pas obtenir de comportement polymorphique de animal_variable code> car cette variable n'est pas associée à un objet code> chien code>. L'objet code> chien code> renvoyé par référence existe toujours parce que vous l'avez créé à l'aide de neuf code>, mais il n'est plus accessible - il a été fui. P>

      1. Comment sur la terre suis-je censé renvoyer une référence à un objet nouvellement généré sans faire la chose stupide que j'ai faite ci-dessus dans Rfunc? (Au moins j'ai entendu cela est stupide.) Li> ol> BlockQuote>

        Le problème est que vous pouvez créer un objet de trois manières. p>

        { // the following expressions evaluate to ...  
         Animal local;  
         // an object that will be destroyed when control exits this block  
         Animal();  
         // an unamed object that will be destroyed immediately if not bound to a reference  
         new Animal();  
         // an unamed Animal *pointer* that can't be deleted unless it is assigned to a Animal pointer variable.  
         {  
          // doing other stuff
         }  
        } // <- local destroyed
        


0 commentaires

0
votes

Si vous souhaitez retourner un type polymorphique à partir d'une méthode et que vous ne voulez pas l'affecter sur le tas, vous pouvez envisager de créer un champ dans la classe de cette méthode et de rendre la fonction de retourner un pointeur de la classe de base de la classe de base. vous voulez.


0 commentaires