Qu'est-ce qu'un bon modèle de classe / de conception existant pour la construction / initialisation multi-étages d'un objet en C ++? P>
J'ai une classe avec certains membres de données qui devraient être initialisés dans différents points du flux du programme, leur initialisation doit donc être retardée. Par exemple, un argument peut être lu à partir d'un fichier et d'un autre du réseau. P>
Actuellement, j'utilise actuellement Boost :: Facultatif pour la construction retardée des membres de données, mais cela me dérange que le facultatif est sémantiquement différent de la construction de retard. P>
Ce dont j'ai besoin de rappeler les fonctionnalités de Boost de Boost :: Linge et Lambda Application de fonction partielle, et utilisez ces bibliothèques, je peux probablement concevoir une construction à plusieurs étages - mais je préfère utiliser des classes testées existantes. (Ou peut-être qu'il y a un autre modèle de construction multi-étapes que je ne connais pas). P>
6 Réponses :
Je ne connais aucun modèle à faire face à ce problème spécifique. C'est une question de conception délicate et une un peu unique à des langues comme C ++. Un autre problème est que la réponse à cette question est étroitement liée à votre style de codage individuel (ou corporatif).
J'utiliserais des pointeurs pour ces membres et quand ils doivent être construits, les affectent en même temps. Vous pouvez utiliser Auto_PTR pour cela et vérifiez contre NULL pour voir si elles sont initialisées. (Je pense que les pointeurs sont un type "en option" intégré en C / C ++ / Java, il existe d'autres langues dans lesquelles NULL n'est pas un pointeur valide). P>
Un problème comme une question de style est que Vous vous comptez peut-être sur vos constructeurs pour faire trop de travail. Lorsque je suis codé Oo, j'ai les constructeurs juste assez de travail pour obtenir l'objet dans un état cohérent. Par exemple, si j'ai une classe code> image code> et que je veux lire à partir d'un fichier, je pourrais faire ceci: p> ou, je pourrais faire Ceci: p> Il peut être difficile de raisonner sur la manière dont un programme C ++ fonctionne si les constructeurs ont beaucoup de code dans eux, surtout si vous posez la question "Que se passe-t-il Si un constructeur échoue? " C'est le principal avantage de déplacer le code des constructeurs. P> J'aurais plus à dire, mais je ne sais pas ce que vous essayez de faire de la construction retardée. P> Edit: Je me suis souvenu qu'il existe un moyen (un peu pervers) d'appeler un constructeur sur un objet à tout moment arbitraire. Voici un exemple: p> class Counter {
public:
Counter(int &cref) : c(cref) { }
void incr(int x) { c += x; }
private:
int &c;
};
void dontTryThisAtHome() {
int i = 0, j = 0;
Counter c(i); // Call constructor first time on c
c.incr(5); // now i = 5
new(&c) Counter(j); // Call the constructor AGAIN on c
c.incr(3); // now j = 3
}
Auto_Ptr est une suggestion fine, mais je ne suis pas d'accord avec votre suggestion de favoriser. Ceux-ci devraient être utilisés seulement i> si le code a besoin i> Configuration paresseux / retardée comme le fait l'OP; Le choix habituel devrait être de rendre les CTORS nécessitent toutes les informations nécessaires. Permettre à presque rien de CTORS signifie que chaque méthode qui fait quelque chose que quelque chose doit vérifier si elle a été initialisée "suffisamment" - alors ne vous chargeez pas avec ces chèques à moins que vous ne l'iez vraiment pas.
En outre, en utilisant la placement nouveau code> pour construire un objet sur le dessus d'un objet existant est dangereux car le destructeur du 1er objet ne sera jamais exécuté! Donc, à moins que vous soyez sûr que la classe a un dteur trivial, cela n'est presque jamais une bonne idée.
Utiliser Boost.Optional ressemble à une bonne solution pour certains cas d'utilisation. Je n'ai pas beaucoup joué avec ça, donc je ne peux pas commenter beaucoup. Une chose que je garde à l'esprit lorsque vous traitez avec une telle fonctionnalité, c'est que je puisse utiliser des constructeurs surchargés au lieu de constructeurs par défaut et de copier.
Lorsque j'ai besoin de cette fonctionnalité, je voudrais simplement utiliser un pointeur sur le type de champ nécessaire comme celui-ci: < / p> Ensuite, au lieu d'utiliser le pointeur, j'accéderais au champ via la méthode suivante: p> J'ai omis -Le sémantique adoptée p> p>
Vous pouvez passer 0 code> à
Supprimer code>, aucun
si (champ _) code> nécessaire.
Merci, je n'étais pas sûr que ce soit spécifique à la mise en œuvre
Le moyen le plus simple que je connaisse est similaire à la technique suggérée par Dietrich EPP, sauf que cela vous permet de retarder vraiment la construction d'un objet jusqu'à un moment de votre choix.
Fondamentalement: réservez l'objet à l'aide de Malloc au lieu de nouveaux (Ainsi contourner le constructeur), appelez ensuite le nouvel opérateur surchargé lorsque vous vraiment em> voulez construire l'objet via placement neuf. p> Exemple: P> Object *x = (Object *) malloc(sizeof(Object));
//Use the object member items here. Be careful: no constructors have been called!
//This means you can assign values to ints, structs, etc... but nested objects can wreak havoc!
//Now we want to call the constructor of the object
new(x) Object(params);
//However, you must remember to also manually call the destructor!
x.~Object();
free(x);
//Note: if you're the malloc and new calls in your development stack
//store in the same heap, you can just call delete(x) instead of the
//destructor followed by free, but the above is the correct way of
//doing it
Le placement Nouveau peut être utilisé pour la construction retardée, mais la dépendance de taille serait sur les paramètres utilisés pour construire l'objet - non sur la taille de (objet). Le problème est donc beaucoup plus large et le cas général a des dépendances (éventuellement d'autres que la taille) sur les paramètres
Oui, comme je le mentionne, il est très douteux pour les objets imbriqués.
Je dois manquer quelque chose ici - je fais ce genre de chose tout le temps. Il est très courant d'avoir des objets importants et / ou non nécessaires par une classe dans toutes les circonstances. Alors créez-les de manière dynamique!
struct Big { char a[1000000]; }; class A { public: A() : big(0) {} ~A() { delete big; } void f() { makebig(); big->a[42] = 66; } private: Big * big; void makebig() { if ( ! big ) { big = new Big; } } };
Cela fonctionne, mais je suggérerais std :: auto_ptr code> en raison de la sécurité ajoutée (fonctionne parfaitement bien pour fonctionner ou où).
Hmm, ce n'est pas Rassembler les paramètres et retarde la construction jusqu'à ce que tout soit présent b>.
@Hassan essayez comme je le ferais, je ne trouve pas cette phrase dans la question.
@Travis Je suggérerais de ne pas - il n'y a pas de sécurité supplémentaire sur le code que j'ai fourni et empêche efficacement la copie d'A, devrait être nécessaire.
"La construction retardée des membres de données" et "donc leur initialisation doit être retardée". "Par exemple, un argument peut être lu depuis un fichier et un autre du réseau" implique la collecte.
@Hassan Ma solution fait retarder la construction et donc l'initialisation. En ce qui concerne le rassemblement, cela semble être votre lecture. J'admets ma solution peut ne pas traiter directement de la question clarifiée, bien que la clarification n'ait pas fonctionné pour moi.
@Neil, il vous a donné beaucoup plus d'informations que les phrases simples que j'ai soulignées pour vous ... Il est déjà passé de votre main à la main trop simplifiée. Il a utilisé BIND et Lambda - donc qu'est-ce que votre petite extrait de code lui donnerait - Il n'a rien sur Lambda et ne lie.
@Hassan Si vous avez pris la peine de regarder l'historique de la question de la question, vous verrez que ceux-ci ont été ajoutés après avoir répondu à la question. Mais pourquoi gaspillez-vous du temps à commenter ma réponse? Pourquoi ne pas vous fournir un vous-même?
@Neil parce que je sais que je ne peux pas construire une réponse élégante, car j'ai toujours résolu cette question de manière spécifique à un but (la dernière était un format de sérialisation personnalisé et son IDL, réduisant spécifiquement les frais généraux de la sérialisation en effectuant une construction en plusieurs étapes). Je suis en mesure de poser la même question qu'il a.
Le problème clé est de savoir si vous devez ou non distinguer des objets complètement remplis d'objets incomplètement peuplés Si vous distinguez des objets complètement peuplés d'objets incomplètement peuplés au niveau de type, vous pouvez appliquer l'exigence d'une fonction être transmis un objet complet. Pour ce faire, je suggérerais de créer un type correspondant Cela fonctionne merveilleusement si vous ne faites pas vraiment doivent faire em> rien avec l'objet avant qu'il ne soit complètement peuplé (peut-être autre que des trucs triviaux comme obtenir les valeurs de champ). Si vous devez parfois traiter un Bien qu'il puisse sembler l'héritage ici est "Retour à l'avant", ce faisant de cette façon signifie qu'un à l'origine, j'avais boost :: facultatif code> ou similaire comme vous le faites: cela facilite la codification rapidement. OTOH, vous ne pouvez pas obtenir le compilateur pour faire respecter l'exigence qu'une fonction particulière nécessite un objet complètement peuplé; Vous devez effectuer une vérification du temps d'exécution des champs à chaque fois.
Types de groupe de paramètres H2>
xparams code> pour chaque type
x code>.
XParams CODE> a
Boost :: Facultatif CODE> Membres et Setter Fonctions pour chaque paramètre pouvant être défini après la construction initiale. Ensuite, vous pouvez forcer
x code> pour avoir un seul constructeur (non-copie), qui prend un
xparams code> comme unique argument et vérifie que chaque paramètre nécessaire a été défini à l'intérieur de celui-ci < Code> xparams code> objet. (Je ne sais pas si ce modèle a un nom - quelqu'un aime modifier ceci pour nous remplir?) P>
"Objet partiel" Types h2>
x code> incomplètement peuplé, comme un "code complet"> x code>, vous pouvez plutôt faire
x code> dérive d'un type
XPartial code>, qui contient toutes les méthodes de la logique, plus
protégée code> pour effectuer des tests de préconditionnement qui testent si tous les champs nécessaires sont remplis. Ensuite, si
x code> garantit qu'il ne peut jamais être construit que dans un état complètement peuplulé, il peut remplacer ces méthodes protégées avec des contrôles triviaux qui renvoient toujours
true p>: P>
x code> peut être fourni en toute sécurité n'importe où un
xpartial & Code> est demandé. Cette approche obéit donc le Principe de substitution de Liskov . Cela signifie qu'une fonction peut utiliser un type de paramètre de
x & code> pour indiquer qu'il a besoin d'un objet complet
x code> objet ou
xpartial & code> pour l'indiquer peut gérer partiellement. Objets peuplés - Dans quel cas un objet
xpartial code> ou un
x code> peut être transmis. p>
iScomplete () < / code> comme
protégé code>, mais trouvé cela ne fonctionnait pas depuis
x code> CORT CORT et attribution doit appeler cette fonction sur leur
XPartial & code> argument, et ils n'ont pas un accès suffisant. À la réflexion, il est plus logique d'exposer publiquement cette fonctionnalité. P> p>
Merci au hasard Hacker, bonne réponse. Avez-vous vu ce motif partiel utilisé / mis en œuvre n'importe où?
J'ai utilisé le motif "groupe de paramètres" plusieurs fois moi-même en bon effet, bien que je ne l'ai pas vu vint ailleurs. Je suis sûr que cela doit être un "modèle connu" d'une sorte cependant. Je n'ai pas réellement utilisé le modèle "objet partiel" moi-même, je viens de le faire maintenant après avoir réfléchi pendant un moment, alors ymmv;)
Je ne sais pas s'il y a un modèle formel pour cela. Dans les endroits où je l'ai vu, nous l'avons appelé "paresseux", "demande" ou "à la demande". P>
Qu'est-ce qui vous dérange exactement? Facultatif a un
bool supplémentaire code> de l'état, y a-t-il une autre différence? Vous avez certainement besoin de cet état si une erreur se produit et que vous devez détruire l'objet maître avant qu'il ne fonctionne à l'achèvement.
Ce qui me dérange, c'est que "facultatif" n'est pas la même chose que "must-être construit - mais peut-être pas encore". Une réelle différence serait que Boost :: Les objets facultatifs ne jettent pas d'exceptions lorsqu'ils sont déréférents. Cela a du sens sémantiquement parce que l'objet est facultatif, vous devriez donc vérifier s'il est initialisé avant de le déséroférer. La déséroférance d'un objet construit par retard devrait toutefois lancer une exception, tout comme n'importe quelle classe de réseau qui est accessible en dehors des limites devrait lancer une exception.
Peut-être qu'un exemple de code simple aiderait à clarifier votre question?
Pour vous rassurer: les foncteurs IMO et le modèle de commande (Lambda + Bind) sont des blocs de construction parfaitement sensibles pour la construction retardée. Vous pouvez utiliser une poignée d'objet et une collection de foncteurs (commandes) qui construisent ledit objet. La collecte de foncteurs déclenchent uniquement lorsqu'un ensemble de foncteurs d'initialisation requis sont présents. Vous voudrez peut-être regarder Books.google.co.uk/... chapitre 5.