@yann_lo_san:
Ta réponse est techniquement correcte, mais on évite pourtant de faire cela :(. Le souci d'utiliser du "virtual", c'est que ce n'est pas "gratuit". Quand on veut appliquer une contrainte conceptuelle, ça ne doit être qu'une vue de l'esprit et ne pas affecter les performances. Le virtual introduit un petit overhead (un pointeur en plus pour la vtable et des accès aux méthodes très légèrement plus lente). De plus, une méthode virtuelle ne peut plus être inliné. Ce n'est pas très cher si tu n'es pas dans un contexte de haute performance (utilisation d'un appel de fonction virtuelle au sein d'une boucle très très utilisé par exemple), mais c'est dommage que le concept puisse affecter les performances (même faiblement).
Bien évidemment, il existe une solution pour avoir le beurre et l'argent du beurre ! On utilise pour cela un CRTP (curiously recurring template pattern) pour faire du pseudo model checking. La technique consiste à utiliser un template pour lui faire vérifier la présence de certaines méthodes. Le template ne faisant rien, le compilateur le retira lors de la passe d'optimisation. C'est donc "gratuit". Le seul petit défaut (même souci que pour la class abstraite au final), c'est que le compilateur ne préviendra que le modèle n'est pas respecté, uniquement lorsque tu essaieras d'instancier l'objet.
#include <iostream>
#include <unistd.h>
#include <stdio.h>
/*
L'astuce consiste à déclarer un pointeur sur fonction sur
une méthode qu'on considère présente. Si celle-ci n'est pas
là, la compilation échouera.
*/
template <typename T>
struct ISerializable
{
typedef T super;
ISerializable()
{
void (super::*checkSave)(FILE* file) = &super::save;
void (super::*checkLoad)(FILE* file) = &super::load;
checkSave = checkSave; // Avoid warning
checkLoad = checkLoad;
}
};
class MonObjet : public ISerializable<MonObjet>
{
public:
MonObjet() : _data(0) {}
MonObjet(int data) : _data(data) {}
void save(FILE* file) { fwrite(&_data, sizeof(int), 1, file); fclose(file); }
void load(FILE* file) { fread(&_data, sizeof(int), 1, file); fclose(file); }
protected:
int _data;
};
class MonObjetIncompatible : public ISerializable<MonObjetIncompatible>
{
public:
void test() { }
};
int main()
{
MonObjet objet(9999); // Ok !
objet.save( fopen("test.txt", "wb") );
// Ailleur ou plus tard, remonte l'objet à son état antérieur
MonObjet* sameObjet = new MonObjet;
sameObjet->load(fopen("test.txt", "rb"));
delete sameObjet;
MonObjetIncompatible obj; // Oupss, ne compilera pas.
return 0;
}
2ème remarque:
Le design de code de l'exemple choisi est un peu maladroit. En effet, en C++, pour tout ce qui est flux on utilise un design un peu plus élégant. On utilise un std::ostream (qui représente un flux quelconque), et on y met dedans le flux choisi (fichier, console, socket, pipe, etc...).
Exemple:
#include <iostream>
#include <fstream>
class MonObjet
{
public:
MonObjet() : _data(0) {}
MonObjet(int data) : _data(data) {}
void save(std::ostream& out) const { out << _data; }
void load(std::ostream& in) { in >> _data; }
private:
int _data;
};
int main()
{
std::ofstream file("toto.txt");
MonObjet obj(56);
obj.save(std::cout);
obj.save(file);
obj.load(std::cin);
obj.load(file);
// envoyer sur le réseau, un pipe, etc... ? => obj.save(socket), obj.save(pipe), etc...
return 0;
}
Améliorer votre expérience CodeS-SourceS avec ce plugin:
ttp://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature