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(); }
8 Réponses :
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 2) 3) Vous n'êtes pas censé. C'est ce que font les pointeurs. P>
edit forte> (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)? P>
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 std :: Shared_ptr code> ou
std :: auto_ptr code>). (Vous pouvez également revenir par copie, mais cela est incompatible avec l'utilisation de l'opérateur
nouveau code>; il est également légèrement incompatible avec le polymorphisme.) P>
rfunc code> est juste faux. Ne fais pas ça. Si vous avez utilisé
Nouveau code> pour créer l'objet, retournez-la via un pointeur (éventuellement enveloppé). P>
null code> 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.) P>
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/...
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());
}
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 .
(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> mais le compilateur permet: p> Animal* a = pFunc(); // polymorphism maintained!
Animal& a = rFunc(); // polymorphism maintained!
Euh non. Dans le cas de animal & a = rfunc () code>, comment supprimez-vous l'objet lorsque vous en avez terminé?
@Dan je sais que, le problème Splitting i> le problème disparaît ... pas la fuite de mémoire :)
Ok, mais n'encourage pas les débutants ;-)
Point 1: N'utilisez pas de références. Utilisez des pointeurs. P>
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. p>
Si vous essayez de mettre en œuvre une relation, telle que p>
Virtual Bool Animal :: mange (animal * autre) = 0; p>
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. P>
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. P>
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
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.
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).
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. P> blockQuote>
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";)). P>
Sinon, essayez d'utiliser un pointeur intelligent avec une sémantique appropriée. Personne sans penser que
Supprimer & * quelque_boost_shared_ptr; code> est une bonne idée. P>
Mais disons-vous que je veux une fonction qui retourne une valeur animale qui est vraiment un chien. p>
- 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>
- 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>
- 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
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. p>
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 :(