É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 surFoo
). Cette différence peut être atténuée par la présence d'un ensemble de constructeurs modélisés dans la sectionwrapper<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 :
-
hotobject_stack_helper
est construit (en poussant le nom sur la pile de contexte) -
T
est construit -- y compris la construction de chacun desT
Les membres de l'entreprise (les hotvars) - 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 ?