Comment mettre en œuvre la méthode de motif en C++ correctement
Il y a une chose en C++ ce qui a été fait me sentir mal à l'aise pendant un temps assez long, parce que honnêtement, je ne sais pas comment le faire, même si cela semble simple:
Comment implémenter la Méthode de factorisation dans C++ correctement?
But: permettre au client d'instancier un objet à l'aide de méthodes de fabrique à la place de l'objet constructeurs, sans conséquences inacceptables et un gain de performance.
Par "méthode de Fabrique de la tendance", je veux dire à la fois statique de l'usine de méthodes à l'intérieur d'un objet ou des méthodes définies dans une autre classe, ou des fonctions globales. En général, "le concept de la redirection de la voie normale de l'instanciation de la classe X de n'importe où ailleurs que le constructeur".
Permettez-moi de parcourir quelques réponses possibles à qui j'ai pensé.
0) Ne pas faire d'usines, de faire des constructeurs.
Cela sonne bien (et, en effet, souvent, la meilleure solution), mais n'est pas un remède universel. Tout d'abord, il y a des cas lors de la construction de l'objet est une tâche assez complexe pour justifier son extraction à une autre classe. Mais même en mettant de ce fait de côté, même pour de simples objets en utilisant seulement les constructeurs souvent de ne pas le faire.
L'exemple le plus simple je sais, c'est un 2-D de la classe Vector. Tellement simple, et pourtant délicat. Je veux être en mesure de construire les deux à partir à la fois Cartésien et des coordonnées polaires. Évidemment, je ne peux pas faire:
struct Vec2 {
Vec2(float x, float y);
Vec2(float angle, float magnitude); //not a valid overload!
//...
};
Ma façon naturelle de penser est alors:
struct Vec2 {
static Vec2 fromLinear(float x, float y);
static Vec2 fromPolar(float angle, float magnitude);
//...
};
Qui, au lieu de constructeurs, me conduit à l'utilisation de la statique des méthodes de fabrique... ce qui signifie essentiellement que je suis en œuvre le modèle de fabrique, en quelque sorte ("la classe devient sa propre usine"). Cela a l'air sympa (et le costume de ce cas particulier), mais échoue dans certains cas, que je vais décrire dans le point 2. Ne lisez la suite.
un autre cas: en essayant de surcharge par deux opaque typedefs de certaines API (tels que les Guid des domaines non reliés, ou un GUID et un champ de bits), les types sémantiquement totalement différentes (donc - en théorie - valide les surcharges), mais qui se révèlent être la même chose - comme des entiers non signés ou nulle pointeurs.
1) La Java Façon
Java est simple, car nous n'avons que dynamique-les objets alloués. Faire un factory est aussi trivial que:
class FooFactory {
public Foo createFooInSomeWay() {
//can be a static method as well,
// if we don't need the factory to provide its own object semantics
// and just serve as a group of methods
return new Foo(some, args);
}
}
En C++, cela se traduit par:
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
};
Cool? Souvent, en effet. Mais cela oblige l'utilisateur à utiliser l'allocation dynamique. Allocation statique est ce qui fait de C++ complexe, mais est aussi ce qui souvent rend puissant. Aussi, je crois qu'il existe certaines cibles (mot-clé: embedded) qui ne permettent pas l'allocation dynamique. Et cela ne signifie pas que les utilisateurs de ces plates-formes, comme pour écrire nettoyer la POO.
De toute façon, la philosophie de côté: Dans le cas général, je ne veux pas forcer les utilisateurs de l'usine pour être retenue pour l'allocation dynamique.
2) Retour par valeur
OK, donc nous savons que 1) c'est cool quand on veut l'allocation dynamique. Pourquoi ne veut-on pas ajouter allocation statique sur le dessus de qui?
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooInSomeWay() {
return Foo(some, args);
}
};
Quoi? Nous ne pouvons pas la surcharge par le type de retour? Oh, bien sûr, nous ne le pouvons pas. Donc, nous allons changer les noms de méthode pour refléter cela. Et oui, j'ai écrit le code non valide exemple ci-dessus, juste à souligner combien je n'aime pas la nécessité de changer le nom de la méthode, par exemple, parce que nous ne pouvons pas mettre en œuvre un indépendant de la langue de conception d'usines correctement maintenant, puisque nous avons un changement de noms et de tous les utilisateurs de ce code vous devez vous rappeler que la différence de la mise en œuvre de la spécification.
class FooFactory {
public:
Foo* createDynamicFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooObjectInSomeWay() {
return Foo(some, args);
}
};
OK... il nous l'avons. C'est moche, comme nous avons besoin de changer le nom de la méthode. C'est imparfait, car nous avons besoin d'écrire le même code deux fois. Mais une fois fait, il fonctionne. Droit?
Eh bien, en général. Mais parfois il ne le fait pas. Lors de la création de Foo, nous avons en fait dépendre du compilateur de faire le retour optimisation de la valeur pour nous, parce que la norme C++ est assez bienveillant pour les éditeurs de compilateurs de ne pas préciser quand l'objet créé en lieu et quand doit-il être copié lors du retour temporaire de l'objet par valeur en C++. Donc, si Toto est cher pour copier, cette approche est risquée.
Et si Toto n'est pas copiables à tous? Eh bien, doh. (Noter qu'en C++17 avec garantie de copie élision, pas-être-copiables est pas un problème de plus pour le code ci-dessus)
Conclusion: Faire une usine en retournant un objet est en effet une solution pour certains cas (comme le 2-D de vecteur mentionné précédemment), mais toujours pas un remplacement pour les constructeurs.
3) en Deux phases de construction
Une autre chose que quelqu'un serait probablement arriver, c'est de séparer la question de l'allocation des objets et de son initialisation. Il en résulte généralement dans le code comme ceci:
class Foo {
public:
Foo() {
//empty or almost empty
}
//...
};
class FooFactory {
public:
void createFooInSomeWay(Foo& foo, some, args);
};
void clientCode() {
Foo staticFoo;
auto_ptr<Foo> dynamicFoo = new Foo();
FooFactory factory;
factory.createFooInSomeWay(&staticFoo);
factory.createFooInSomeWay(&dynamicFoo.get());
//...
}
On peut penser qu'il fonctionne comme un charme. Le seul prix que nous payons pour notre code...
Depuis que j'ai écrit tout cela et à gauche ce que la dernière, je n'aime pas trop. 🙂 Pourquoi?
Tout d'abord... sincèrement, je n'aime pas le concept de deux phases de construction et je me sens coupable lorsque je l'utilise. Si je crée mes objets avec l'affirmation que "si elle existe, elle est dans l'état valide", j'ai l'impression que mon code est plus sûr et moins sujette aux erreurs. J'aime cette façon.
Les abandons de la convention ET de l'évolution de la conception de mon objet juste pour le but de l'usine de fabrication est.. bien, lourd.
Je sais que le ci-dessus ne pas convaincre beaucoup de gens, permettez-moi de donner plus d'arguments solides. En utilisant deux phases de construction, vous ne pouvez pas:
- initialiser
const
ou membre de référence variables, - passer des arguments à la base de constructeurs de classe et d'objet de membre des constructeurs.
Et probablement il pourrait y en avoir plus d'inconvénients que je ne peux pas penser à l'instant, et je ne le sent même pas particulièrement obligés de depuis la au-dessus des points de balle de me convaincre, déjà.
Donc: pas même à proximité d'une bonne solution générale pour la mise en œuvre d'une usine.
Conclusions:
Nous voulons avoir un moyen de l'instanciation d'objets qui:
- permettre l'uniforme de l'instanciation, indépendamment de la répartition,
- donner des noms significatifs pour les méthodes de construction (donc pas en s'appuyant sur l'argument de la surcharge),
- de ne pas introduire de façon significative les performances frappé et, de préférence, une importante augmentation du code frapper, surtout à côté client,
- être général, comme dans: possible être mis en place pour toute la classe.
Je crois que j'ai prouvé que les solutions que j'ai mentionné ne pas répondre à ces exigences.
Un indice? Merci de me fournir une solution, je ne veux pas penser que cette langue ne me permet pas de mettre en œuvre correctement un tel trivial concept.
- double de stackoverflow.com/questions/4992307/...
- bien que le titre est très similaire, les questions sont à mon humble avis différents.
- Bon en double, mais le texte de cette question est précieux en soi.
- Voudriez-vous pas un constructeur privé? La réflexion sur ce problème un temps, j'ai pensé que le meilleur moyen d'encapsuler la création de l'objet était d'utiliser un gestionnaire de classe, ce qui était un ami d'un constructeur privé. Ce serait forcer de tout objet extérieur par le biais de l'usine de fonctions que le gestionnaire de la propriété.
- non, plutôt "allez, vous êtes comme une marque de voiture, comment se fait je ne trouve pas une seule voiture qui n'est pas cassé? ils vendent des bonnes voitures partout ailleurs et j'attends la même chose de vous".
- Je pense que votre premier exemple est vraiment décrivant le "nommé constructeur" l'idiome pas un modèle de fabrique (en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Constructor)
- Je suis d'accord que cette question est plus complet, mais il est néanmoins essentiellement la même question: "Comment puis-je utiliser correctement l'usine modèle de conception en C++?"
- Le point de mon commentaire, c'est que cette question doit rester récupérés et non fusionné, même si elle est fermée comme un doublon.
- Umm, na pas vous dire: Foo* FooFactory::createDynamicFooInSomeWay(); Foo FooFactory::createStaticFooInSomeWay()
- J'ai pris la liberté de modifier l'exemple de ce qui question d'origine. J'allais à l'appel de la seconde createStaticFooInSomeWay(), mais ce n'était pas de droite c'est plus comme createDynamicallyAllocatedCFooInsomewayandreturnptr(), et createFooValueAndReturnCopy(), mais ceux-ci sont trop verbeux. (Si il y a une convention de nommage pour ce genre de chose, je voudrais entendre parler d'elle.)
- Merci pour le modifier, c'est une bonne prise.
- Deux ans après avoir demandé cela, j'ai quelques points à ajouter: 1) Cette question est pertinente pour plusieurs modèles de conception ([résumé] de l'usine, de constructeur, vous le nom, je n'aime pas fouiller dans leur taxonomie). 2) Le problème réel dont il est ici question est "comment proprement dissocier l'objet d'allocation de stockage de construction de l'objet?".
- Euh, vous êtes à l'alinéa 2) ne permet pas de décrire
static
de répartition, c'est l'allocation de pile, alias local, akaauto
en C++ - Ne pas l'allocation dynamique de l'option de vous donner en C++ créer une fuite de mémoire?
- seulement si vous n'avez pas
delete
il. Ce genre de méthodes sont parfaitement bien, tant qu'il est "documenté" (code source de la documentation 😉 ) que l'appelant prend possession du pointeur (lire: est responsable de la suppression au moment opportun). - vous pourriez aussi le faire très explicite par le retour d'un
unique_ptr<T>
au lieu deT*
. - Je ne comprends pas ce qui est le point d'une usine lorsque vous n'êtes pas en utilisant des pointeurs: si vous n'êtes pas en utilisant les pointeurs (et, par conséquent, polymorphisme), alors vous devez connaître le type de béton lorsque vous avez écrit le code. Alors, pourquoi avez-vous besoin d'une usine, alors? Et si vous êtes à la pensée de la farce de la pile () de l'objet en un pointeur, eh bien, c'est un peu impliqué et je ne vois pas où/quand il pourrait être utile (même si, ne jamais dire jamais).
- Je me trouve dans cette position parce que je veux unittest une classe qui crée des objets à l'intérieur de certaines de ses méthodes, et je n'ai pas tout tas. J'ai donc besoin de l'allocation de pile, et j'ai besoin de pouvoir varier les types selon qu'il s'agit de production ou de test de code.
Vous devez vous connecter pour publier un commentaire.
Je crois que ce point est incorrect. La complexité n'a pas vraiment d'importance. La pertinence de ce fait. Si un objet peut être construit en une seule étape (pas comme dans le générateur de modèle), le constructeur est le bon endroit pour le faire. Si vous avez vraiment besoin d'une autre classe pour effectuer le travail, alors il devrait être une classe helper qui est utilisé à partir du constructeur de toute façon.
Il est facile de contourner ce:
Le seul inconvénient est qu'il ressemble un peu verbeux:
Mais la bonne chose est que vous pouvez voir immédiatement ce type de coordonnée que vous utilisez, et en même temps vous n'avez pas à vous soucier de la copie. Si vous souhaitez copier, et c'est cher (comme le prouve le profilage, bien sûr), vous pouvez utiliser quelque chose comme Qt partagé de classes afin d'éviter de copier les frais généraux.
Comme pour l'allocation de type, la principale raison d'utiliser le modèle de fabrique est généralement de polymorphisme. Les constructeurs ne peuvent pas être virtuel, et même si on le pouvait, il ne serait pas beaucoup de sens. Lors de l'utilisation de statique ou de l'allocation de pile, vous ne pouvez pas créer des objets dans un polymorphe car le compilateur a besoin de connaître la taille exacte. Donc, il ne fonctionne qu'avec des pointeurs et des références. Et renvoie une référence à partir d'une usine ne fonctionne pas trop, parce qu'un objet techniquement peut être supprimé par référence, il peut être assez déroutant et bug sur le ventre, voir C'est la pratique de retour d'un C++ variable de référence, le mal? par exemple. Donc, les pointeurs sont la seule chose qui reste, et qui comprend des pointeurs intelligents aussi. En d'autres termes, les usines sont plus utiles lorsqu'il est utilisé avec l'allocation dynamique, de sorte que vous pouvez faire des choses comme ceci:
Dans d'autres cas, les usines juste de l'aide pour résoudre les problèmes mineurs comme ceux avec des surcharges que vous avez mentionnés. Ce serait bien si il était possible de les utiliser d'une manière uniforme, mais il ne fait pas de mal bien qu'il est probablement impossible.
Vec2(Cartesian(x, y))
comme une sorte de sucre syntaxique pour indiquer le système de coordonnées. Vous aurez seulement besoin de différentes surcharges lors de l'écriture de constructeurs pour une sous-classe, mais c'est toujours mieux qu'une usine de méthode.member function already defined or declared
erreur sur le deuxième constructeur même si je ne suis pas en train d'utiliser effectivement les constructeurs.Polar
etCartesian
, les premières questions que je pose la question "à Quoi bon la Vec2 classe maintenant?"Polar
etCartesian
tous au lieu deVec2
. Fondamentalement, il n'ya pas beaucoup l'interface deVec2
outre les opérations mathématiques (par la surcharge des opérateurs pour vos types), la conversion de/vers l'un de l'autre (par la définition des opérateurs de conversion), les normes (par la définition d'un seul surchargé la fonction libre). La surcharge est une partie de C++, et agit comme un gros invisible au moment de la compilation si-sinon sur les types. La surcharge d'opérateur et de l'implicite/explicite les conversions sont aussi de la partie C++. Ils sont des fonctionnalités ajoutées à C++. Nous ne devrions pas le combat de la langue.Simple Usine Exemple:
unique_ptr
dans cet exemple n'ont pas de surcharge de performance. La gestion des ressources, y compris la mémoire, est l'un de la cour suprême avantages du C++ plus de toute autre langue, parce que vous pouvez le faire sans pénalité de performances et de façon déterministe, sans perdre le contrôle, mais vous, vous dites exactement le contraire. Certaines personnes n'aiment pas les choses C++ ne implicitement, comme la gestion de la mémoire par le biais de pointeurs intelligents, mais si ce que vous voulez est pour tout être obligatoirement explicite, utilisez C; le compromis est un ordre de grandeur moins de problèmes. Je pense que c'est injuste, vous votez en bas d'une bonne recommandation.boost::ptr_vector<Foo>
parstd::vector<std::unique_ptr<Foo>>
plus rares adaptations.boost::ptr_vector<>
est un tout petit peu plus efficace, car il comprend qu'il détient le pointeur plutôt que de déléguer les travaux à un sous-classe. MAIS le principal avantage deboost::ptr_vector<>
est qu'elle expose ses membres par référence (pointeur) ainsi, il est vraiment facile à utiliser avec des algorithmes de la bibliothèque standard.Avez-vous pensé à pas à l'aide d'une usine à tous, et au lieu de faire de belle utilisation du type de système? Je pense à deux approches différentes qui font ce genre de chose:
Option 1:
Qui permet d'écrire des choses comme:
Option 2:
vous pouvez utiliser des "balises" comme le TSL ne avec les itérateurs et ces. Par exemple:
Cette seconde approche permet d'écrire du code qui ressemble à ceci:
qui est aussi agréable et expressif tout en vous permettant d'avoir unique en des prototypes pour chaque constructeur.
Vous pouvez lire une très bonne solution: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus
La meilleure solution est de les "commentaires et discussions", voir la "Pas besoin pour statique de Créer des méthodes".
À partir de cette idée, j'ai fait une usine. Notez que j'utilise Qt, mais vous pouvez changer QMap et QString pour les mst équivalents.
Exemple d'utilisation:
Je plutôt d'accord avec la accepté de répondre, mais il y a un C++11 option qui n'a pas été couverts dans les réponses existantes:
Exemple:
Ensuite, vous pouvez construire des objets sur la pile:
Que les sous-objets d'autres choses:
Ou allouée dynamiquement:
Quand pourrais-je l'utiliser?
Si, sur un constructeur public, il n'est pas possible de donner un initialisers pour tous les membres de la classe sans calcul préalable, alors je risque de les convertir au constructeur d'une méthode statique. La méthode statique effectue les calculs préliminaires, puis renvoie une valeur résultat par un constructeur privé qui n'a tout simplement un membre de la sage-initialisation.
Je dis " pourrait', car il dépend de l'approche qui donne le plus clair de code sans être inutilement inefficace.
Loki a à la fois une Méthode De Fabrique et un Résumé De L'Usine. Les deux sont documentés (beaucoup) dans Modern C++ Design, par Andei Alexandrescu. L'usine méthode est sans doute plus proche de ce que vous semblez être, après, si c'est toujours un peu différent (au moins si ma mémoire est bonne, il vous oblige à inscrire un type avant de l'usine peut créer des objets de ce type).
Function
et le type de manipulations peut être remplacé parstd::function
et<type_traits>
et tandis que les lambdas, de filetage, de rvalue refs ont des implications qui peuvent nécessiter quelques petits bidouillages, il n'existe aucune norme de remplacement pour les singletons des usines comme il le décrit.Je ne cherche pas à répondre à toutes mes questions, car je crois qu'il est trop large. Juste un couple de notes:
Que la classe est en fait une Constructeur, plutôt que d'une Usine.
Alors vous pourriez avoir votre usine de l'encapsuler dans un pointeur intelligent. Je crois que de cette façon, vous pouvez avoir votre gâteau et le manger aussi.
Cela élimine également les questions liées au retour par valeur.
En effet. Tous les modèles de conception de leurs (propres à une langue) des contraintes et des inconvénients. Il est recommandé d'utiliser uniquement quand ils vous aider à résoudre votre problème, pas pour leur propre bien.
Si vous êtes après le "parfait" de l'usine de mise en œuvre, et bien, bonne chance.
Modèle De Fabrique
Et si vous le compilateur ne supporte pas la Valeur de Retour d'Optimisation, fossé, il n'a probablement pas contenir beaucoup de l'optimisation à tout...
Factory
est qu'il est assez générique et couvre beaucoup de terrain, une usine peut ajouter des arguments (en fonction de l'environnement/de l'installation) ou offrir une aide à la mise en cache (liées à la Mouche/Piscines) par exemple, mais ces cas n'ont de sens que dans certaines situations.Je sais que cette question a été répondu il y a 3 ans, mais ce peut être ce que vous recherchez.
Google, a publié il y a quelques semaines une bibliothèque permettant de facilité et de souplesse dynamique allocations d'objets. Ici, il est: http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html
C'est mon c++11 style de solution. le paramètre 'base' est pour la classe de base de tous les sous-classes. créateurs, std::function objets pour créer des sous-instances de classe, peut-être un liant à votre sous-classe membre statique de la fonction 'créer(certains args)'. Ce peut-être pas parfait, mais qui fonctionne pour moi. Et c'est un peu le "général" de la solution.
Un exemple de l'utilisation.