J’ai entendu dire qu’un moyen d’écrire du code Cross Platform c ++ est de définir les classes comme suit (par exemple, une classe Window):
window.h window_win32.cpp window_linux.cpp window_osx.cpp
puis choisissez le fichier d’implémentation en conséquence. Mais que faire si j’ai des membres de cette classe qui sont relatifs à l’OS? Comme un membre HWND
pour l’implémentation Win32. Je ne peux pas le mettre dans le window.h
ou quand window.h
de le comstackr, disons, Linux, cela générerait une erreur du compilateur.
#ifdef
-je besoin de #ifdef
cela? J’ai déjà posé une question similaire, mais celle-ci est davantage axée sur ce problème particulier.
Il existe plus de moyens de résoudre ce problème – chacun a ses avantages et ses inconvénients.
1.) Utilisez les macros #ifdef, #endif
// Note: not sure if "WINDOWS" or "WIN32" or something else is defined on Windows #ifdef WINDOWS #include #else // etc. #endif class MyClass { public: // Public interface... private: #ifdef WINDOWS HWND m_myHandle; #else // etc. #endif };
Avantages:
Les inconvénients:
2.) Comme il y avait déjà été écrit, vous pourriez utiliser le polymorphism:
// IMyClass.h for user of your class: class IMyClass { public: virtual ~IMyClass() {} virtual void doSomething() = 0; }; // MyClassWindows.h is implementation for one platform #include #include "IMyClass.h" class MyClassWindows : public IMyClass { public: MyClassWindows(); virtual void doSomething(); private: HWND m_myHandle; }; // MyClassWindows.cpp implements methods for MyClassWindows
Avantages:
Les inconvénients:
3.) idiome PIMPL.
// MyClass.h class MyClass { public: MyClass(); void doSomething(); private: struct MyClassImplementation; MyClassImplementation *m_impl; } // MyClassWindows.h #include #include "MyClass.h" struct MyClassImplementation { HWND m_myHandle; void doSomething(); }
Dans ce cas, MyClassImplementation conserve toutes les données nécessaires (au moins spécifiques à une plate-forme) et implémente ce qui est nécessaire (à nouveau, spécifique à une plate-forme). Dans MyClass.cpp, vous incluez l’implémentation spécifique à la plate-forme (les méthodes peuvent être en ligne), dans le constructeur (ou plus tard, si vous voulez – soyez prudent) vous allouez l’implémentation et dans le destructeur, vous la détruisez.
Avantages:
Les inconvénients:
4.) Définissez un type neutre, suffisamment grand pour conserver vos données. Par exemple long long int.
// MyClass.h class MyClass { public: MyClass(); void doSomething(); private: typedef unsigned long long int MyData; MyData m_data; };
Dans la mise en œuvre (par exemple, MyClassWindows.cpp), vous devez toujours transtyper (réinterpréter la conversion) entre MyClass :: MyData et les données réelles stockées.
Avantages:
Les inconvénients:
Utilisez donc celui qui convient le mieux à votre problème … et à votre personnalité: 3 car avec le pouvoir actuel, ils sont tous quatre plus ou moins relativement égaux en termes de vitesse et d’espace.
L’intérêt d’une telle approche est que vous encapsuliez des données spécifiques au système d’exploitation dans le fichier spécifique au système d’exploitation. Si vous devez contourner un HWND, vous pouvez reconsidérer la conception de votre object. Le sens d’une telle structure dépend de la taille de votre code spécifique à un système d’exploitation. Vous ne voulez pas vraiment compresser toutes les classes possibles dans un seul fichier.
D’autre part, il existe des bibliothèques pour les interfaces graphiques qui font exactement cela – encapsulant les composants spécifiques au système d’exploitation dans une bibliothèque telle que QT ou wxWidgets ou autres. Si vous séparez correctement l’interface graphique du code principal, vous n’aurez peut-être même pas besoin de cette approche.
J’utilise une telle structure dans mon projet pour prendre en charge différentes versions de xerces, sans que le code principal ne soit encombré par #ifdefs
. Cependant, dans mon cas, le code affecté est plutôt petit. Je peux imaginer qu’un framework d’interface graphique prend beaucoup plus d’espace.
J’appendais seulement un 5) point à la réponse de @Laethnes
5) Ecrivez un en-tête vide qui inclut, au moment de la compilation, l’en-tête de la plate-forme et masquez la classe spécifique à la plate-forme sous une typedef
// MyClass.hpp #if defined(WINDOWS) # include "WINMyClass.hpp" typedef WINMyClass MyClass #elif defined(OSX) # include "OSXMyClass.hpp" typedef OSXMyClass MyClass ... // keep going #endif
Avantages:
Les inconvénients:
Une méthode courante consiste à utiliser le polymorphism. Vous fournissez une interface qui résume vos fonctionnalités sans aucun égard pour une plate-forme spécifique:
class thread { virtual ~thread() {} virtual void run() = 0; /* whatever */ };
et vous pouvez ensuite hériter de cette classe à l’aide de fonctionnalités spécifiques à la plate-forme:
class posix_thread : thread;
au moment de la compilation, vous choisissez avec #ifdef
classe que vous incluez et instanciez.
La plupart du temps, vous évitez de telles choses dans l’en-tête. Cependant, il y a des moments où vous voulez les exposer. Dans de tels cas, ma solution a toujours été d’utiliser quelque chose comme:
#include dependentInclude(syst,MyInclude.lhh)
dependInclude étant une macro qui étend le chemin du fichier dont j’ai besoin, en fonction de la valeur de syst (définie sur la ligne de commande).