3 votes

Quelles différences détectables y a-t-il entre une classe et sa classe de base ?

Étant donné le modèle suivant :

template <typename T>
class wrapper : public T {};

Quelles différences visibles dans l'interface ou le comportement y a-t-il entre un objet de type Foo et un objet de type wrapper<Foo> ?

J'en connais déjà un :

  • wrapper<Foo> n'a qu'un constructeur nullaire, un constructeur de copie et un opérateur d'affectation (et il ne les a que si ces opérations sont valables sur Foo ). Cette différence peut être atténuée par la présence d'un ensemble de constructeurs modélisés dans la section wrapper<T> qui transmettent des valeurs au constructeur T.

Mais je ne sais pas quelles autres différences détectables il pourrait y avoir, ni s'il existe des moyens de les dissimuler.


(Edit) Exemple concret

Certaines personnes semblent demander un contexte pour cette question, voici donc une explication (quelque peu simplifiée) de ma situation.

J'écris fréquemment du code dont les valeurs peuvent être réglées pour ajuster les performances et le fonctionnement précis du système. J'aimerais avoir un moyen facile (faible surcharge de code) d'exposer de telles valeurs par le biais d'un fichier de configuration ou de l'interface utilisateur. Je suis en train d'écrire une bibliothèque pour me permettre de le faire. La conception prévue permet d'utiliser quelque chose comme ceci :

class ComplexDataProcessor {
    hotvar<int> epochs;
    hotvar<double> learning_rate;
public:
    ComplexDataProcessor():
        epochs("Epochs", 50),
        learning_rate("LearningRate", 0.01)
        {}

    void process_some_data(const Data& data) {
        int n = *epochs;
        double alpha = *learning_rate;
        for (int i = 0; i < n; ++i) {
            // learn some things from the data, with learning rate alpha
        }
    }
};

void two_learners(const DataSource& source) {
    hotobject<ComplexDataProcessor> a("FastLearner");
    hotobject<ComplexDataProcessor> b("SlowLearner");
    while (source.has_data()) {
        a.process_some_data(source.row());
        b.process_some_data(source.row());
        source.next_row();
    }
}

Lorsqu'il est exécuté, il configure ou lit les valeurs de configuration suivantes :

FastLearner.Epochs
FastLearner.LearningRate
SlowLearner.Epochs
SlowLearner.LearningRate

Il s'agit d'un code inventé (il se trouve que mon cas d'utilisation n'est même pas l'apprentissage automatique), mais il montre quelques aspects importants de la conception. Les valeurs modifiables sont toutes nommées, et peuvent être organisées en une hiérarchie. Les valeurs peuvent être regroupées par plusieurs méthodes, mais dans l'exemple ci-dessus, je ne montre qu'une seule méthode : Envelopper un objet dans un hotobject<T> classe. En pratique, le hotobject<T> a un travail assez simple : il doit pousser le nom de l'objet/du groupe sur une pile de contexte locale au thread, puis permettre à l'utilisateur d'accéder à l'objet. T à construire (à ce stade, l'objet hotvar<T> sont construites et vérifient la pile contextuelle pour voir dans quel groupe elles doivent se trouver), puis ouvrent la pile contextuelle.

Cette opération s'effectue comme suit :

struct hotobject_stack_helper {
    hotobject_stack_helper(const char* name) {
        // push onto the thread-local context stack
    }
};

template <typename T>
struct hotobject : private hotobject_stack_helper, public T {
    hotobject(const char* name):
        hotobject_stack_helper(name) {
        // pop from the context stack
    }
};

Pour autant que je sache, l'ordre de construction dans ce scénario est assez bien défini :

  1. hotobject_stack_helper est construit (en poussant le nom sur la pile de contexte)
  2. T est construit -- y compris la construction de chacun des T Les membres de l'entreprise (les hotvars)
  3. Le corps de la hotobject<T> est exécuté, ce qui fait sauter la pile de contexte.

J'ai donc un code de travail pour faire cela. Il reste cependant une question, qui est la suivante : Quels sont les problèmes que je pourrais me causer plus tard en utilisant cette structure. Cette question se réduit en grande partie à la question que je pose en fait : Comment hotobject se comportera-t-il différemment de T lui-même ?

3voto

GManNickG Points 155079

C'est une question étrange, puisque vous devriez poser des questions sur votre utilisation spécifique ("qu'est-ce que je veux faire, et comment cela m'aide ou me nuit"), mais je suppose que c'est en général :

wrapper<T> n'est pas un T donc :

  • Il ne peut pas être construit comme un T . (Comme vous le notez.)
  • Il ne peut pas être converti comme un T .
  • Il perd l'accès à la vie privée T a accès.

Et je suis sûr qu'il y en a d'autres, mais les deux premiers couvrent pas mal de choses.

2voto

Daniel Earwicker Points 63298

Supposons que vous ayez :

class Base {};
class Derived : Base {};

Maintenant vous pouvez dire :

Base *basePtr = new Derived;

Cependant, vous ne pouvez pas dire :

wrapper<Base> *basePtr = new wrapper<Derived>();

C'est-à-dire que, même si leurs paramètres de type peuvent avoir une relation d'héritage, deux types produits par la spécialisation d'un modèle n'ont pas de relation d'héritage.

0voto

Potatoswatter Points 70305

Une référence à un objet est convertible (avec accès) en une référence à un sous-objet de la classe de base. Il existe un sucre syntaxique pour invoquer des conversions implicites vous permettant de traiter l'objet comme une instance de la base, mais c'est vraiment ce qui se passe. Pas plus, pas moins.

La différence n'est donc pas du tout difficile à détecter. Ce sont des choses (presque) complètement différentes. La différence entre une relation "is-a" et une relation "has-a" est la spécification d'un nom de membre.

Quant à la dissimulation de la classe de base, je pense que vous avez répondu par inadvertance à votre propre question. Utilisez l'héritage privé en spécifiant private (ou omettre public pour un class ), et ces conversions n'auront pas lieu en dehors de la classe elle-même, et aucune autre classe ne sera capable de dire qu'une base existe.

0voto

Benoît Points 10901

Si votre classe héritée possède ses propres variables membres (ou au moins une), alors

sizeof(InheritedClass) > sizeof(BaseClass)

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