2010-02-06 3 views
3

C'est un assez basique C++ question de conception:Comment fournir de nombreux constructeurs, mais sans trop de dépendances?

J'ai une classe qui contient des données qui sont en lecture seule, une fois que l'objet est construit:

class Foo { 
private: 
    class Impl; 
    Impl* impl_; 
public: 
    int get(int i); // access internal data elements 
}; 

Maintenant, je voudrais mettre en œuvre plusieurs façons de construire un objet Foo et de le remplir avec des données: à partir de std::istream, à partir d'un itérateur, d'un vecteur, etc. Quelle est la meilleure façon de l'implémenter?

Je peux ajouter tous ces constructeurs directement dans Foo, mais je ne veux pas vraiment l'utilisateur Foo d'avoir à inclure std::istream etc. Je suis aussi inquiet au sujet des classes contenant trop de code.

Quelle est la manière la plus idiomatique de le faire? Je suppose, ajouter une fonction privée addElement, puis définir les fonctions d'usine amis qui créent Foo objets en lisant les données, en appelant addElement et retourner l'objet construit? D'autres options?

Répondre

10

Si vous voulez construire quelque chose à partir d'une plage, peut-être:

class X 
{ 
public: 
    template <class InputIterator> 
    X(InputIterator first, InputIterator last); 
}; 

Utilisation:

//from array 
X a(array, array + array_size); 

//from vector 
X b(vec.begin(), vec.end()); 

//from stream 
X c((std::istream_iterator<Y>(std::cin)), std::istream_iterator<Y>()); 
1

Je peux ajouter tous les constructeurs directement Foo, mais je ne veux pas vraiment l'utilisateur Foo d'avoir d'inclure std :: istream etc. Je suis aussi inquiet au sujet classes contenant trop beaucoup de code; l'utilisateur peut vouloir créer de nombreux objets Foo, mais je ne veux pas chaque objet à contenir beaucoup de code de construction.

Votre constructeur pour std :: istream peut le déclarer et le transmettre en tant que référence. De cette façon, votre utilisateur n'a pas besoin d'inclure istream pour inclure votre Foo.h, mais vous devez inclure istream dans votre Foo.cpp.

Je ne suis pas sûr de comprendre votre deuxième objection. Les objets ne portent pas de code avec eux, seulement des données. En supposant que nous ne parlons pas de modèles, le code n'existe qu'une seule fois. Si un constructeur n'est pas utilisé par le programme, l'éditeur de liens devrait le supprimer. Il ne devrait pas y avoir de gaspillage à fournir de nombreux constructeurs.

+2

Je pense que '' #include ce que l'on cherche (pour les flux à terme déclarant). – UncleBens

1

Je peux ajouter tous les constructeurs directement Foo, mais je ne veux pas vraiment l'utilisateur Foo d'avoir d'inclure std :: istream etc.

S'ils sont construits en utilisant istream, vous devez utiliser les fichiers d'en-tête pertinents dans les fichiers qui utilisent réellement les classes istream.

Je suis aussi inquiet au sujet des classes contenant trop de code; l'utilisateur peut vouloir créer de nombreux objets Foo, mais je ne veut pas que chaque objet contienne des lots de code de construction.

Vous semblez confus. Chaque objet ne contient pas de copie du code - il n'y a qu'une seule copie.

+0

D'accord, c'est vrai. Quand j'ai écrit ceci, je pensais aux discussions sur les fonctions membres par rapport aux fonctions d'amis non membres, et comment certaines personnes déconseillent d'utiliser trop de fonctions membres. Mais je suppose que c'est juste pour des raisons esthétiques ... – Frank

+0

Ce n'est pas pour des raisons esthétiques, c'est pour limiter le nombre de choses qui doivent changer quand la représentation interne de la classe change. Autrement dit, pour augmenter l'encapsulation. Scott Meyer a publié un article sur ce sujet. –

+0

@Dan: Cela n'obtient pas l'encapsulation. Si un membre n'a besoin que d'accéder à l'interface publique, c'est tout ce dont vous avez besoin pour l'implémenter; vous n'avez pas besoin d'accéder directement aux non-publics simplement parce qu'ils sont là. Ce qu'il vous donne est ceci vérifié par le compilateur au lieu d'un coup d'oeil rapide (nom distinctement les non-publics et il est facile de jeter un coup d'oeil), et cela n'a pas assez de valeur pour que je le laisse affecter . (Il y a des raisons importantes pour faire des choses qui ne sont pas membres, c'est juste que ce que vous avez dit est souvent cité mais pas l'un d'entre eux.) –

0

Vous parlez de deux tailles de code source types de code, 1) différents (qui affecte le temps de construction uniquement) et 2) taille de l'exécutable (taille « code compilé », ou le segment de code). Ils sont corrélés, mais certainement pas la même chose.

Le premier est un problème difficile à résoudre en C++, car le langage nécessite des TU autonomes et monolithiques. (Comparez à un langage comme Go, où les concepteurs ont appris à éviter ce problème à partir de leur expérience C.) Templates aide, comme le fait uniquement forward declarations ("déclarations qui ne sont pas des définitions") lorsque vous n'avez pas besoin de définitions. (Bien qu'il soit ironique que les modèles aident, car ils nécessitent tout leur code dans les en-têtes, en pratique.) Les temps de construction les plus longs sont quelque chose que nous avons juste dans le C++ courant. La seconde peut être atténuée avec une méthode d'initialisation commune à chaque appel de ctor ou une base commune. Ce dernier a d'autres avantages, comme lorsque l'initialisation des membres peut ou doit être faite dans la liste d'initialisation. Exemple:

struct SpanBase { 
    SpanBase(int start, int stop, int step) 
    : start(start), stop(stop), step(step) 
    // any complex code in the init list would normally be duplicated 
    // in each Span ctor 
    { 
    IMAGINE("complex code executed by each Span ctor"); 
    if (start > stop) throw std::logic_error("Span: start exceeds stop"); 
    } 
protected: 
    int start, stop, step; 
}; 

struct Span : protected SpanBase { 
    // Protected inheritance lets any class derived from Span access members 
    // which would be protected in Span if SpanBase didn't exist. If Span 
    // does not have any, then private inheritance can be used. 

    Span(int stop) : SpanBase(0, stop, 1) {} 
    Span(int start, int stop) : SpanBase(start, stop, 1) {} 

    Span(int start, int stop, int step): StepBase(start, stop, step) {} 
    // this one could be handled by a default value, but that's not always true 
}; 

Et enfin, C++ 0x vous permet de déléguer d'un cteur à l'autre, ce modèle tout est considérablement simplifiée.

1

Il existe une solution simple: l'utilisation d'une autre classe comme intermédiaire.

struct FooBuild 
{ 
    // attributes of Foo 
}; 

class Foo 
{ 
public: 
    Foo(const FooBuild&); 

private: 
    // attributes of Foo, some of them const 
}; 

Ensuite, tout le monde peut facilement mettre en place FooBuild comme on le souhaite, et construire un objet Foo de cela. De cette façon, vous n'avez pas à fournir trop de constructeurs, et vous pouvez facilement maintenir une classe invariante pour Foo facilement, avec la première validation se produisant dans le constructeur comme d'habitude.

Je pris l'idée de python, et peut-être son frozenset classe :)

Questions connexes