69 votes

Pourquoi ne devrait-on pas dériver de la classe de chaînes c ++ std?

Je voulais vous demander à propos d'un sujet Effective C++.

Il dit:

Un destructeur doit être faite virtuel si une classe doit agir comme un polymorphe de la classe. Il ajoute que, depuis std::string n'ont pas un destructeur virtuel, il ne faut jamais tirer. Aussi std::string n'est même pas conçu pour être une classe de base, oublier polymorphe de la classe de base.

Je ne comprends pas qu'est-ce qui est requis dans une classe pour être admissibles au cours d'une classe de base (pas un polymorphe)?

Est la seule raison que je ne doit pas en tirer de std::string de la classe est de ne pas avoir un destructeur virtuel? Pour la réutilisabilité de l'objet à une classe de base peut être défini et plusieurs dérivés de la classe ne peut hériter que d'elle. Donc, ce qui rend std::string même pas admissibles en tant que classe de base?

Aussi, si il y a une classe de base purement définis pour la réutilisabilité de l'objet et il y a beaucoup de types dérivés, est-il un moyen pour empêcher les clients de faire Base* p = new Derived() , car les classes ne sont pas destinés à être utilisés polymorphically?

63voto

Billy ONeal Points 50631

Je pense que cette déclaration reflète la confusion ici (l'emphase est mienne):

Je ne comprends pas qu'est-ce qui est requis dans une classe pour être admissible à être une base de clas (pas un polymorphes une)?

Dans idiomatiques C++, il y a deux utilisations pour dériver à partir d'une classe:

  • privé de l'héritage, utilisé pour mixin et la programmation orientée aspects à l'aide de modèles.
  • public de l'héritage, utilisé pour polymorphes situations seulement. EDIT: Ok, je suppose que cela pourrait être utilisé dans quelques mixin scénarios, tels que l' boost::iterator_facade -- qui se présentent lorsque le PROGRAMME est en cours d'utilisation.

Il n'y a absolument aucune raison de faire publiquement dériver une classe en C++ si vous êtes pas d'essayer de faire quelque chose polymorphes. La langue dispose d'une connexion fonctionne comme une caractéristique standard de la langue, et des fonctions libres sont ce que vous devriez être en utilisant ici.

Pensez-y de cette façon -- voulez-vous vraiment à forcer les clients de votre code afin de le convertir à l'aide de certains propriétaires classe string tout simplement parce que vous voulez ajouter un peu de méthodes? Parce que, contrairement à Java ou C# (ou les plus similaires langages orientés objet), lorsque vous dérivez une classe en C++, la plupart des utilisateurs de la classe de base besoin de savoir sur ce genre de changement. En Java/C#, les classes sont généralement accessibles par le biais de références, qui sont similaires à C++les pointeurs. Donc, il y a un niveau d'indirection qui découple les clients de votre classe, vous permettant de remplacer une classe dérivée sans les autres clients le savoir.

Toutefois, en C++, les classes sont des types de valeur , contrairement à la plupart des autres langages à objets. La meilleure façon de voir ce qui est connu comme le découpage de problème. Fondamentalement, pensez à:

int StringToNumber(std::string copyMeByValue)
{
    std::istringstream converter(copyMeByValue);
    int result;
    if (converter >> result)
    {
        return result;
    }
    throw std::logic_error("That is not a number.");
}

Si vous passer votre propre chaîne à cette méthode, le constructeur de copie pour std::string seront appelés à en faire une copie, pas le constructeur de copie pour votre objet dérivé -- n'importe quel enfant de la classe d' std::string est passé. Cela peut conduire à une incohérence entre vos méthodes et de tout ce qui est fixé à la chaîne. La fonction StringToNumber ne peut pas simplement prendre quel que soit votre objet dérivé est et de la copie, tout simplement parce que votre objet dérivé probablement a une taille différente de celle d'un std::string - , mais cette fonction a été compilée à la réserve seulement de l'espace pour un std::string de stockage automatique. En Java et C# ce n'est pas un problème parce que la seule chose comme stockage automatique impliqués sont des types référence, et les références sont toujours de la même taille. Pas le cas en C++.

Longue histoire courte, ne pas utiliser l'héritage pour virer de bord sur les méthodes en C++. Ce n'est pas idiomatiques et les résultats dans des problèmes avec la langue. L'utilisation non-ami, non des fonctions de membre si possible, suivie de la composition. Ne pas utiliser l'héritage, sauf si vous êtes modèle de la métaprogrammation ou souhaitez le comportement polymorphique. Pour plus d'informations, voir Scott Meyers Effective C++ Article 23: Préférer la non-membre non-ami de fonctions de fonctions membres.

EDIT: Voici un exemple plus complet montrant le découpage de problème. Vous pouvez voir qu'elle est sortie sur codepad.org

#include <ostream>
#include <iomanip>

struct Base
{
    int aMemberForASize;
    Base() { std::cout << "Constructing a base." << std::endl; }
    Base(const Base&) { std::cout << "Copying a base." << std::endl; }
    ~Base() { std::cout << "Destroying a base." << std::endl; }
};

struct Derived : public Base
{
    int aMemberThatMakesMeBiggerThanBase;
    Derived() { std::cout << "Constructing a derived." << std::endl; }
    Derived(const Derived&) : Base() { std::cout << "Copying a derived." << std::endl; }
    ~Derived() { std::cout << "Destroying a derived." << std::endl; }
};

int SomeThirdPartyMethod(Base /* SomeBase */)
{
    return 42;
}

int main()
{
    Derived derivedObject;
    {
        //Scope to show the copy behavior of copying a derived.
        Derived aCopy(derivedObject);
    }
    SomeThirdPartyMethod(derivedObject);
}

10voto

Bo Persson Points 42821

Non seulement le destructor pas virtuel, std :: string ne contient pas de fonctions virtuelles à tous, et aucun membre protégés. Cela rend très difficile pour la classe dérivée de modifier ses fonctionnalités.

Alors pourquoi en tireriez-vous?

Un autre problème lié à la non-polymorphie est que si vous transmettez votre classe dérivée à une fonction qui attend un paramètre de chaîne, votre fonctionnalité supplémentaire sera simplement coupée en deux et l'objet sera à nouveau vu comme une chaîne.

10voto

beduin Points 2742

Si vous vraiment envie de dériver d’elle (ne pas discuter de pourquoi vous voulez le faire) je pense que vous pouvez empêcher instanciation de classe tas direct en le rendant de privé :

Mais de cette manière vous limitez vous-même de toute dynamique `` objets.

4voto

iammilind Points 29275

Pourquoi ne devrait-on pas tirer de c++ std classe string?

Parce que c'est pas nécessaire. Si vous souhaitez utiliser DerivedString pour la fonctionnalité d'extension; je ne vois pas de problème en dérivant std::string. La seule chose est, vous ne devriez pas interagir entre les deux classes (c'est à dire ne pas utiliser string comme un récepteur pour DerivedString).

Est-il possible de prévenir le client de faire Base* p = new Derived()

Oui. Assurez-vous que vous fournissez inline wrappers autour Base méthodes à l'intérieur d' Derived classe. par exemple

class Derived : protected Base { // 'protected' to avoid Base* p = new Derived
  const char* c_str () const { return Base::c_str(); }
//...
};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X