8
votes

Construction paresseuse / multi-étage en C ++

Qu'est-ce qu'un bon modèle de classe / de conception existant pour la construction / initialisation multi-étages d'un objet en C ++?

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.

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.

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).


4 commentaires

Qu'est-ce qui vous dérange exactement? Facultatif a un bool supplémentaire 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.


6 Réponses :


0
votes

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> xxx pré>

ou, je pourrais faire Ceci: p> xxx pré>

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
}


2 commentaires

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 si le code a besoin 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 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.



0
votes

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> xxx

Ensuite, au lieu d'utiliser le pointeur, j'accéderais au champ via la méthode suivante: xxx

J'ai omis -Le sémantique adoptée


2 commentaires

Vous pouvez passer 0 à Supprimer , aucun si (champ _) nécessaire.


Merci, je n'étais pas sûr que ce soit spécifique à la mise en œuvre



0
votes

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


2 commentaires

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.



1
votes

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;
      }
    }
};


9 commentaires

Cela fonctionne, mais je suggérerais std :: auto_ptr 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 .


@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.



4
votes

Le problème clé est de savoir si vous devez ou non distinguer des objets complètement remplis d'objets incomplètement peuplés au niveau de type . Si vous décidez de ne pas faire de distinction, alors utilisez simplement boost :: facultatif 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

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 xparams pour chaque type x . XParams a Boost :: Facultatif Membres et Setter Fonctions pour chaque paramètre pouvant être défini après la construction initiale. Ensuite, vous pouvez forcer x pour avoir un seul constructeur (non-copie), qui prend un xparams comme unique argument et vérifie que chaque paramètre nécessaire a été défini à l'intérieur de celui-ci < Code> xparams objet. (Je ne sais pas si ce modèle a un nom - quelqu'un aime modifier ceci pour nous remplir?)

"Objet partiel" Types

Cela fonctionne merveilleusement si vous ne faites pas vraiment doivent faire 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 x incomplètement peuplé, comme un "code complet"> x , vous pouvez plutôt faire x dérive d'un type XPartial , qui contient toutes les méthodes de la logique, plus protégée pour effectuer des tests de préconditionnement qui testent si tous les champs nécessaires sont remplis. Ensuite, si x 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 : XXX

Bien qu'il puisse sembler l'héritage ici est "Retour à l'avant", ce faisant de cette façon signifie qu'un x peut être fourni en toute sécurité n'importe où un xpartial & 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 & pour indiquer qu'il a besoin d'un objet complet x objet ou xpartial & pour l'indiquer peut gérer partiellement. Objets peuplés - Dans quel cas un objet xpartial ou un x peut être transmis.

à l'origine, j'avais iScomplete () < / code> comme protégé , mais trouvé cela ne fonctionnait pas depuis x CORT CORT et attribution doit appeler cette fonction sur leur XPartial & argument, et ils n'ont pas un accès suffisant. À la réflexion, il est plus logique d'exposer publiquement cette fonctionnalité.


2 commentaires

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;)



0
votes

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".


0 commentaires