Les classes d'interface sont trÚs largement utilisées dans les programmes C ++. Mais, malheureusement, des erreurs sont souvent commises lors de l'implémentation de solutions basées sur des classes d'interface. L'article décrit comment concevoir correctement les classes d'interface; plusieurs options sont envisagées. L'utilisation de pointeurs intelligents est décrite en détail. Un exemple d'implémentation d'une classe d'exception et d'un modÚle de classe de collection basé sur des classes d'interface est donné.
Table des matiĂšres
Présentation
Une classe d'interface est une classe qui n'a pas de données et se compose principalement de fonctions purement virtuelles. Cette solution vous permet de séparer complÚtement l'implémentation de l'interface - le client utilise la classe d'interface - à un autre endroit, une classe dérivée est créée dans laquelle les fonctions purement virtuelles sont redéfinies et la fonction d'usine est définie. Les détails de l'implémentation sont complÚtement cachés au client. De cette façon, une véritable encapsulation est implémentée, ce qui est impossible avec la classe habituelle. Vous pouvez lire sur les classes d'interface de Scott Meyers [Meyers2]. Les classes d'interface sont également appelées classes de protocole.
L'utilisation de classes d'interface vous permet d'affaiblir les dĂ©pendances entre les diffĂ©rentes parties du projet, ce qui simplifie le dĂ©veloppement de l'Ă©quipe et rĂ©duit le temps de compilation / assemblage. Les classes d'interface facilitent la mise en Ćuvre de solutions flexibles et dynamiques lorsque les modules sont chargĂ©s de maniĂšre sĂ©lective lors de l'exĂ©cution. L'utilisation des classes d'interface en tant que bibliothĂšque d'interface (API) (SDK) simplifie la solution des problĂšmes de compatibilitĂ© binaire.
Les classes d'interface sont utilisées assez largement, avec leur aide, elles implémentent l'interface (API) des bibliothÚques (SDK), l'interface des plug-ins (plugins) et bien plus encore. De nombreux modÚles de Gang of Four [GoF] sont naturellement implémentés à l'aide de classes d'interface. Les classes d'interface incluent les interfaces COM. Mais, malheureusement, des erreurs sont souvent commises lors de l'implémentation de solutions basées sur des classes d'interface. Essayons de clarifier ce problÚme.
1. Fonctions membres spéciales, création et suppression d'objets
Cette section décrit briÚvement un certain nombre de fonctionnalités C ++ que vous devez connaßtre pour bien comprendre les solutions proposées pour les classes d'interface.
1.1. Fonctions spéciales des membres
Si le programmeur n'a pas dĂ©fini les fonctions membres de la classe dans la liste suivante - le constructeur par dĂ©faut, le constructeur de copie, l'opĂ©rateur d'affectation de copie, le destructeur - alors le compilateur peut le faire pour lui. C ++ 11 a ajoutĂ© un constructeur de dĂ©placement et un opĂ©rateur d'affectation de dĂ©placement Ă cette liste. Ces fonctions membres sont appelĂ©es fonctions membres spĂ©ciales. Ils ne sont gĂ©nĂ©rĂ©s que s'ils sont utilisĂ©s et des conditions supplĂ©mentaires spĂ©cifiques Ă chaque fonction sont remplies. Nous attirons l'attention sur le fait que cette utilisation peut s'avĂ©rer assez cachĂ©e (par exemple lors de l'implĂ©mentation de l'hĂ©ritage). Si la fonction requise ne peut pas ĂȘtre gĂ©nĂ©rĂ©e, une erreur est gĂ©nĂ©rĂ©e. (Ă l'exception des opĂ©rations de relocalisation, elles sont remplacĂ©es par des opĂ©rations de copie.) Les fonctions membres gĂ©nĂ©rĂ©es par le compilateur sont publiques et intĂ©grables.
Les fonctions membres spéciales ne sont pas héritées, si une fonction membre spéciale est requise dans la classe dérivée, le compilateur essaiera toujours de la générer; la présence de la fonction membre correspondante définie dans la classe de base par le programmeur n'affecte pas cela.
Le programmeur peut interdire la génération de fonctions membres spéciales, en C ++ 11 il est nécessaire d'utiliser la construction "=delete"
lors de la déclaration, en C ++ 98 déclarer la fonction membre correspondante privée et non définir. Dans l'héritage de classe, l'interdiction de générer une fonction membre spéciale faite dans la classe de base s'applique à toutes les classes dérivées.
Si le programmeur est à l'aise avec les fonctions membres générées par le compilateur, alors en C ++ 11, il peut l'indiquer explicitement, et pas simplement supprimer la déclaration. Pour ce faire, vous devez utiliser la construction "=default"
lors de la déclaration, tandis que le code est mieux lu et que des fonctionnalités supplémentaires apparaissent liées à la gestion du niveau d'accÚs.
Des dĂ©tails sur les fonctions spĂ©ciales des membres peuvent ĂȘtre trouvĂ©s dans [Meyers3].
1.2. Création et suppression d'objets - détails de base
La création et la suppression d'objets à l'aide des opérateurs new/delete
est une opération deux-en-un typique. Lors de l'appel de new
, la mémoire est d'abord allouée à l'objet. Si la sélection réussit, le constructeur est appelé. Si le constructeur lÚve une exception, la mémoire allouée est libérée. Lorsque l'opérateur de delete
est appelé, tout se passe dans l'ordre inverse: d'abord, le destructeur est appelé, puis la mémoire est libérée. Le destructeur ne doit pas lever d'exceptions.
Si le new
opérateur est utilisé pour créer un tableau d'objets, la mémoire est d'abord allouée à l'ensemble du tableau. Si la sélection réussit, le constructeur par défaut est appelé pour chaque élément du tableau à partir de zéro. Si un constructeur lÚve une exception, alors pour tous les éléments créés du tableau, le destructeur est appelé dans l'ordre inverse de l'appel du constructeur, la mémoire allouée est libérée. Pour supprimer un tableau, vous devez appeler l'opérateur delete[]
(appelé l'opérateur delete
pour les tableaux), et pour tous les éléments du tableau, le destructeur est appelé dans l'ordre inverse de l'appel du constructeur, puis la mémoire allouée est libérée.
Attention! Vous devez appeler la forme correcte de l'opérateur de delete
, selon qu'un seul objet ou tableau est supprimĂ©. Cette rĂšgle doit ĂȘtre strictement respectĂ©e, sinon vous pouvez obtenir un comportement indĂ©fini, c'est-Ă -dire que tout peut arriver: fuites de mĂ©moire, crash, etc. Voir [Meyers2] pour plus de dĂ©tails.
Les fonctions d'allocation de mémoire standard ne std::bad_alloc
Ă satisfaire la requĂȘte, std::bad_alloc
une exception de type std::bad_alloc
.
Il est sûr d'appliquer n'importe quelle forme de l'opérateur de delete
Ă un pointeur nul.
Dans la description ci-dessus, une clarification est nĂ©cessaire. Pour les soi-disant types triviaux (types intĂ©grĂ©s, structures de style C), le constructeur ne peut pas ĂȘtre appelĂ© et le destructeur ne fait en aucun cas quoi que ce soit. Voir Ă©galement la section 1.6.
1.3. Niveau d'accĂšs destructeur
Lorsque l'opérateur de delete
est appliquĂ© Ă un pointeur sur une classe, le destructeur de cette classe doit ĂȘtre disponible au point d'appel de delete
. (Il existe une exception à cette rÚgle, abordée dans la section 1.6.) Ainsi, en sécurisant ou en fermant le destructeur, le programmeur interdit l'utilisation de l'opérateur de delete
lorsque le destructeur n'est pas disponible. Souvenez-vous que si aucun destructeur n'est défini dans la classe, le compilateur le fera seul et ce destructeur sera ouvert (voir section 1.1).
1.4. Créez et supprimez en un seul module
Si le new
opérateur a créé un objet, alors l'opérateur de delete
doit se trouver dans le mĂȘme module pour le delete
. Au figurĂ©, "mettez-le lĂ oĂč vous l'avez pris". Cette rĂšgle est bien connue, voir par exemple [Sutter / Alexandrescu]. Si cette rĂšgle est violĂ©e, une «non-concordance» des fonctions d'allocation et de libĂ©ration de mĂ©moire peut se produire, ce qui, en rĂšgle gĂ©nĂ©rale, entraĂźne une interruption anormale du programme.
1.5. Suppression polymorphe
Si vous concevez une hiérarchie polymorphe de classes dont les instances sont supprimées à l'aide de l'opérateur de delete
, il doit y avoir un destructeur virtuel ouvert dans la classe de base, cela garantit que le destructeur du type réel de l'objet est appelé lorsque l'opérateur de delete
est appliqué au pointeur sur la classe de base. Si cette rÚgle est violée, un appel au destructeur de classe de base peut se produire, ce qui peut entraßner une fuite de ressources.
1.6. Suppression lorsque la déclaration de classe est incomplÚte
Le caractÚre omnivore de l'opérateur de delete
peut crĂ©er certains problĂšmes; il peut ĂȘtre appliquĂ© Ă un pointeur de type void*
ou à un pointeur sur une classe qui a une déclaration incomplÚte (préemptive). Dans ce cas, aucune erreur ne se produit, seul l'appel au destructeur est ignoré, seule la fonction de libération de la mémoire est appelée. Prenons un exemple:
class X;
Ce code se compile mĂȘme si une dĂ©claration de classe X
complĂšte n'est pas disponible sur le pair de delete
numérotation. Vrai, lors de la compilation (Visual Studio), un avertissement est émis:
warning C4150: deletion of pointer to incomplete type 'X'; no destructor called
S'il existe une implémentation de X
et CreateX()
, le code est CreateX()
, si CreateX()
renvoie un pointeur vers l'objet créé par le new
opérateur, alors l'appel Foo()
correctement exécuté, le destructeur n'est pas appelé. Il est clair que cela peut conduire à un épuisement des ressources, donc encore une fois sur la nécessité de faire attention aux avertissements.
Cette situation n'est pas farfelue, elle peut facilement survenir lors de l'utilisation de classes telles que des pointeurs intelligents ou des classes de descripteurs. Scott Meyers traite de ce problĂšme dans [Meyers3].
2. Fonctions purement virtuelles et classes abstraites
Le concept de classes d'interface est basé sur des concepts C ++ tels que les fonctions virtuelles pures et les classes abstraites.
2.1. Fonctions virtuelles pures
Une fonction virtuelle déclarée à l'aide de la construction "=0"
est appelée virtuelle pure.
class X {
Contrairement Ă une fonction virtuelle classique, une fonction purement virtuelle ne peut pas ĂȘtre dĂ©finie (Ă l'exception du destructeur, voir section 2.3), mais elle doit ĂȘtre redĂ©finie dans l'une des classes dĂ©rivĂ©es.
Des fonctions purement virtuelles peuvent ĂȘtre dĂ©finies. Emblem Sutter propose plusieurs utilisations utiles pour cette fonction [Shutter].
2.2. Classes abstraites
Une classe abstraite est une classe qui a au moins une fonction purement virtuelle. Une classe dĂ©rivĂ©e d'une classe abstraite et ne remplaçant pas au moins une fonction purement virtuelle sera Ă©galement abstraite. La norme C ++ interdit la crĂ©ation d'instances d'une classe abstraite; vous ne pouvez crĂ©er que des instances de dĂ©rivĂ©s de classes non abstraites. Ainsi, une classe abstraite est créée pour ĂȘtre utilisĂ©e comme classe de base. Par consĂ©quent, si un constructeur est dĂ©fini dans une classe abstraite, alors cela n'a pas de sens de le rendre ouvert, il doit ĂȘtre protĂ©gĂ©.
2.3. Destructeur virtuel pur
Dans certains cas, il est conseillé de faire un destructeur virtuel pur. Mais cette solution a deux fonctionnalités.
- Un destructeur purement virtuel doit ĂȘtre dĂ©fini. (La dĂ©finition par dĂ©faut est gĂ©nĂ©ralement utilisĂ©e, c'est-Ă -dire en utilisant la construction
"=default"
.) Le destructeur de classe dérivé appelle des destructeurs de classe de base tout au long de la chaßne d'héritage et, par conséquent, la file d'attente est garantie d'atteindre la racine - un destructeur purement virtuel. - Si le programmeur n'a pas redéfini un destructeur virtuel pur dans la classe dérivée, le compilateur le fera pour lui (voir section 1.1). Ainsi, une classe dérivée d'une classe abstraite avec un destructeur purement virtuel peut perdre son caractÚre abstrait sans écraser explicitement le destructeur.
Un exemple d'utilisation d'un destructeur virtuel pur peut ĂȘtre trouvĂ© dans la section 4.4.
3. Classes d'interface
Une classe d'interface est une classe abstraite qui n'a pas de données et se compose principalement de fonctions purement virtuelles. Une telle classe peut avoir des fonctions virtuelles ordinaires (non purement virtuelles), par exemple, un destructeur. Il peut également y avoir des fonctions membres statiques, telles que des fonctions d'usine.
3.1. Implémentations
Une implĂ©mentation d'une classe d'interface sera appelĂ©e une classe dĂ©rivĂ©e dans laquelle des fonctions purement virtuelles sont redĂ©finies. Il peut y avoir plusieurs implĂ©mentations de la mĂȘme classe d'interface, et deux schĂ©mas sont possibles: horizontal, lorsque plusieurs classes diffĂ©rentes hĂ©ritent de la mĂȘme classe d'interface, et vertical, lorsque la classe d'interface est la racine de la hiĂ©rarchie polymorphe. Bien sĂ»r, il peut y avoir des hybrides.
Le point clé du concept de classes d'interface est la séparation complÚte de l'interface de l'implémentation - le client ne travaille qu'avec la classe d'interface, l'implémentation n'est pas disponible pour elle.
3.2. Création d'objets
L'inaccessibilitĂ© de la classe d'implĂ©mentation provoque certains problĂšmes lors de la crĂ©ation d'objets. Le client doit crĂ©er une instance de la classe d'implĂ©mentation et obtenir un pointeur sur la classe d'interface via laquelle l'objet sera accessible. Ătant donnĂ© que la classe d'implĂ©mentation n'est pas disponible, vous ne pouvez pas utiliser le constructeur, par consĂ©quent, la fonction d'usine est utilisĂ©e, qui est dĂ©finie du cĂŽtĂ© de l'implĂ©mentation. Cette fonction crĂ©e gĂ©nĂ©ralement un objet Ă l'aide du new
opĂ©rateur et renvoie un pointeur sur l'objet créé, transtypĂ© en pointeur sur une classe d'interface. Une fonction de fabrique peut ĂȘtre un membre statique d'une classe d'interface, mais elle n'est pas nĂ©cessaire, par exemple, elle peut ĂȘtre membre d'une classe de fabrique spĂ©ciale (qui, Ă son tour, peut elle-mĂȘme ĂȘtre une classe d'interface) ou une fonction libre. Une fonction d'usine peut renvoyer non pas un pointeur brut vers une classe d'interface, mais un pointeur intelligent. Cette option est discutĂ©e dans les sections 3.3.4 et 4.3.2.
3.3. Supprimer un objet
La suppression d'un objet est une opĂ©ration extrĂȘmement critique. Une erreur entraĂźne une fuite de mĂ©moire ou une double suppression, ce qui entraĂźne gĂ©nĂ©ralement un plantage du programme. Ci-dessous, ce problĂšme est considĂ©rĂ© aussi dĂ©taillĂ© que possible, avec une grande attention portĂ©e Ă la prĂ©vention des actions erronĂ©es des clients.
Il existe quatre options principales:
- Utilisation de l'opérateur de
delete
. - Utilisation d'une fonction virtuelle spéciale.
- Utilisation d'une fonction externe.
- Suppression automatique Ă l'aide du pointeur intelligent.
3.3.1. Utilisation de l'opérateur de delete
Pour ce faire, vous devez avoir un destructeur virtuel ouvert dans la classe d'interface. Dans ce cas, l'opérateur de delete
, appelé pour un pointeur vers une classe d'interface cÎté client, fournit un appel au destructeur de la classe d'implémentation. Cette option peut fonctionner, mais il est difficile de la reconnaßtre comme réussie. Nous recevons des appels des new
opérateurs et delete
différents cÎtés de la «barriÚre», new
du cÎté de l'implémentation, delete
du cÎté client. Et si l'implémentation de la classe d'interface se fait dans un module séparé (ce qui est assez courant), alors nous obtenons une violation de la rÚgle de la section 1.4.
3.3.2. Utilisation d'une fonction virtuelle spéciale
Plus progressive est une autre option: la classe d'interface doit avoir une fonction virtuelle spéciale qui supprime l'objet. Une telle fonction, au final, revient à appeler delete this
, mais cela se produit dĂ©jĂ du cĂŽtĂ© de l'implĂ©mentation. Une telle fonction peut ĂȘtre appelĂ©e de diffĂ©rentes maniĂšres, par exemple, Delete()
, mais d'autres options sont également utilisées: Release()
, Destroy()
, Dispose()
, Free()
, Close()
, etc. En plus de suivre la rÚgle de la section 1.4, cette option présente plusieurs avantages supplémentaires.
- Vous permet d'utiliser des fonctions personnalisées d'allocation / désallocation de mémoire pour la classe d'implémentation.
- Vous permet d'implémenter un schéma plus complexe pour contrÎler la durée de vie de l'objet d'implémentation, par exemple, à l'aide d'un compteur de référence.
Dans ce mode de réalisation, une tentative de suppression d'un objet à l'aide de l'opérateur de delete
peut ĂȘtre compilĂ©e et mĂȘme effectuĂ©e, mais il s'agit d'une erreur. Pour l'empĂȘcher dans la classe d'interface, il suffit d'avoir un destructeur protĂ©gĂ© vide ou purement virtuel (voir section 1.3). Notez que l'utilisation de l'opĂ©rateur de delete
peut ĂȘtre assez masquĂ©e, par exemple, les pointeurs intelligents standard utilisent l'opĂ©rateur de suppression pour supprimer un objet par dĂ©faut et le code correspondant est profondĂ©ment enfoui dans leur implĂ©mentation. Un destructeur protĂ©gĂ© vous permet de dĂ©tecter toutes ces tentatives au stade de la compilation.
3.3.3. Utilisation d'une fonction externe
Cette option peut attirer une certaine symétrie des procédures de création et de suppression d'un objet, mais en réalité elle n'a aucun avantage par rapport à la version précédente, mais il existe de nombreux problÚmes supplémentaires. Cette option n'est pas recommandée pour une utilisation et n'est pas envisagée à l'avenir.
3.3.4. Suppression automatique Ă l'aide du pointeur intelligent
Dans ce cas, la fonction d'usine ne renvoie pas un pointeur brut vers une classe d'interface, mais un pointeur intelligent correspondant. Ce pointeur intelligent est créé cĂŽtĂ© implĂ©mentation et encapsule l'objet de suppression, qui supprime automatiquement l'objet implĂ©mentation lorsque le pointeur intelligent (ou sa derniĂšre copie) sort du domaine cĂŽtĂ© client. Dans ce cas, une fonction virtuelle spĂ©ciale pour supprimer l'objet d'implĂ©mentation peut ne pas ĂȘtre requise, mais un destructeur protĂ©gĂ© est toujours nĂ©cessaire, il est nĂ©cessaire d'empĂȘcher l'utilisation erronĂ©e de l'opĂ©rateur de delete
. (Certes, il convient de noter que la probabilité d'une telle erreur est sensiblement réduite.) Cette option est examinée plus en détail à la section 4.3.2.
3.4. Autres options pour gérer la durée de vie d'une instance d'une classe d'implémentation
Dans certains cas, le client peut recevoir un pointeur vers la classe d'interface, mais ne le possĂšde pas. La gestion de la durĂ©e de vie de l'objet d'implĂ©mentation est entiĂšrement du cĂŽtĂ© de l'implĂ©mentation. Par exemple, un objet peut ĂȘtre un objet singleton statique (cette solution est typique pour les usines). Un autre exemple est liĂ© Ă l'interaction bidirectionnelle, voir la section 3.7. Le client ne doit pas supprimer un tel objet, mais un destructeur protĂ©gĂ© pour une telle classe d'interface est nĂ©cessaire, il est nĂ©cessaire d'empĂȘcher l'utilisation erronĂ©e de l'opĂ©rateur de delete
.
3.5. Copier la sémantique
Pour une classe d'interface, la création d'une copie de l'objet d'implémentation à l'aide du constructeur de copie n'est pas possible, donc si la copie est requise, la classe doit avoir une fonction virtuelle qui crée une copie de l'objet d'implémentation et renvoie un pointeur vers la classe d'interface. Une telle fonction est souvent appelée constructeur virtuel et son nom traditionnel est Clone()
ou Duplicate()
.
L'utilisation de l'opĂ©rateur d'affectation de copie n'est pas interdite, mais ne peut pas ĂȘtre considĂ©rĂ©e comme une bonne idĂ©e. L'opĂ©rateur d'affectation de copie est toujours associĂ©; il doit ĂȘtre associĂ© au constructeur de copie. L'opĂ©rateur gĂ©nĂ©rĂ© par le compilateur par dĂ©faut n'a pas de sens, il ne fait rien. Il est thĂ©oriquement possible de dĂ©clarer un opĂ©rateur d'affectation purement virtuel avec redĂ©finition ultĂ©rieure, mais l'affectation virtuelle n'est pas une pratique recommandĂ©e, des dĂ©tails peuvent ĂȘtre trouvĂ©s dans [Meyers1]. De plus, l'affectation semble trĂšs peu naturelle: l'accĂšs aux objets de la classe d'implĂ©mentation se fait gĂ©nĂ©ralement via un pointeur vers la classe d'interface, donc l'affectation ressemblera Ă ceci:
* = *;
Il est préférable d'interdire à l'opérateur d'affectation, et si nécessaire, une telle sémantique a dans la classe d'interface la fonction virtuelle correspondante.
Il existe deux façons d'interdire l'attribution.
- Déclarez l'opérateur d'affectation supprimé (
=delete
). Si les classes d'interface forment une hiérarchie, cela suffit pour le faire dans la classe de base. L'inconvénient de cette méthode est qu'elle affecte la classe d'implémentation, l'interdiction s'y applique également. - Déclarez une instruction d'affectation protégée avec une définition par défaut (
=default
). Cela n'affecte pas la classe d'implĂ©mentation, mais dans le cas d'une hiĂ©rarchie de classes d'interface, une telle annonce doit ĂȘtre faite dans chaque classe.
3.6. Constructeur de classe d'interface
Souvent, le constructeur d'une classe d'interface n'est pas dĂ©clarĂ©. Dans ce cas, le compilateur gĂ©nĂšre le constructeur par dĂ©faut nĂ©cessaire pour implĂ©menter l'hĂ©ritage (voir section 1.1). Ce constructeur est ouvert, bien que suffisant pour ĂȘtre sĂ©curisĂ©. Si dans la classe d'interface le constructeur copiant est dĂ©clarĂ© supprimĂ© ( =delete
), alors la gĂ©nĂ©ration par le compilateur du constructeur est supprimĂ©e par dĂ©faut, et un tel constructeur doit ĂȘtre explicitement dĂ©clarĂ©. Il est naturel de le sĂ©curiser avec une dĂ©finition par dĂ©faut ( =default
). En principe, la déclaration d'un tel constructeur protégé peut toujours se faire. Un exemple est dans la section 4.4.
3.7. Interaction bidirectionnelle
Les classes d'interface sont pratiques pour utiliser la communication bidirectionnelle. Si certains modules sont accessibles via des classes d'interface, le client peut également créer des implémentations de certaines classes d'interface et leur transmettre des pointeurs dans le module. Grùce à ces pointeurs, le module peut recevoir des services du client et également transmettre des données ou des notifications au client.
3.8. Pointeurs intelligents
Ătant donnĂ© que l'accĂšs aux objets de la classe d'implĂ©mentation se fait gĂ©nĂ©ralement via un pointeur, il est naturel d'utiliser des pointeurs intelligents pour contrĂŽler leur durĂ©e de vie. Mais il faut garder Ă l'esprit que si la deuxiĂšme option pour supprimer des objets est utilisĂ©e, alors avec le pointeur intelligent standard, il est nĂ©cessaire de transfĂ©rer un suppresseur utilisateur (type) ou une instance de ce type. Si cela n'est pas fait, le pointeur intelligent utilisera l'opĂ©rateur de suppression pour supprimer l'objet, et le code ne sera tout simplement pas compilĂ© (grĂące au destructeur protĂ©gĂ©). Les pointeurs intelligents standard (y compris l'utilisation de dissolvants personnalisĂ©s) sont discutĂ©s en dĂ©tail dans [Josuttis], [Meyers3]. Un exemple d'utilisation d'un dĂ©capant personnalisĂ© peut ĂȘtre trouvĂ© dans la section 4.3.1.
, , , .
3.9. -
- const. , , -, .
3.10. COM-
COM- , , COM â , COM- , C, , . COM- C++ , COM.
3.11.
(API) (SDK). . -, -, . , (Windows DLL), : -. . , , . LoadLibrary()
, -, .
4.
4.1.
, .
class IBase { protected: virtual ~IBase() = default;
.
class IActivatable : public IBase { protected: ~IActivatable() = default;
, , . , IBase
. , (. 1.3). , .
4.2.
class Activator : private IActivatable {
, , , - , .
4.3.
4.3.1.
. - ( IBase
):
struct BaseDeleter { void operator()(IBase* p) const { p->Delete(); } };
std::unique_ptr<>
- :
template <class I> // I â IBase using UniquePtr = std::unique_ptr<I, BaseDeleter>;
, , - , UniquePtr
.
-:
template <class I> // I â - CreateInstance() UniquePtr<I> CreateInstance() { return UniquePtr<I>(I::CreateInstance()); }
:
template <class I> // I â IBase UniquePtr<I> ToPtr(I* p) { return UniquePtr<I>(p); }
std::shared_ptr<>
std::unique_ptr<>
, , std::shared_ptr<>
. Activator
.
auto un1 = CreateInstance<IActivatable>(); un1->Activate(true); auto un2 = ToPtr(IActivatable::CreateInstance()); un2->Activate(true); std::shared_ptr<IActivatable> sh = CreateInstance<IActivatable>(); sh->Activate(true);
( â -):
std::shared_ptr<IActivatable> sh2(IActivatable::CreateInstance());
std::make_shared<>()
, ( ).
: , . : , - . 4.4.
4.3.2.
. -. std::shared_ptr<>
, , ( ). std::shared_ptr<>
( ) - , delete
. std::shared_ptr<>
- ( ) - . .
#include <memory> class IActivatable; using ActPtr = std::shared_ptr<IActivatable>; // class IActivatable { protected: virtual ~IActivatable() = default; // IActivatable& operator=(const IActivatable&) = default; // public: virtual void Activate(bool activate) = 0; static ActPtr CreateInstance(); // - }; // class Activator : public IActivatable { // ... public: Activator(); // ~Activator(); // void Activate(bool activate) override; }; Activator::Activator() {/* ... */} Activator::~Activator() {/* ... */} void Activator::Activate(bool activate) {/* ... */} ActPtr IActivatable::CreateInstance() { return ActPtr(new Activator()); }
- std::make_shared<>()
:
ActPtr IActivatable::CreateInstance() { return std::make_shared<Activator>(); }
std::unique_ptr<>
, , - , .
4.4.
C# Java C++ «», . . IBase
.
class IBase { protected: IBase() = default; virtual ~IBase() = 0;
, Delete()
, .
IBase::~IBase() = default; void IBase::Delete() { delete this; }
IBase
. Delete()
, . - IBase
. Delete()
, - . Delete()
, . , 4.3.1.
5. ,
5.1
, , , , .
, , IException
Exception
.
class IException { friend class Exception; virtual IException* Clone() const = 0; virtual void Delete() = 0; protected: virtual ~IException() = default; public: virtual const char* What() const = 0; virtual int Code() const = 0; IException& operator=(const IException&) = delete; }; class Exception { IException* const m_Ptr; public: Exception(const char* what, int code); Exception(const Exception& src) : m_Ptr(src.m_Ptr->Clone()) {} ~Exception() { m_Ptr->Delete(); } const IException* Ptr() const { return m_Ptr; } };
Exception
, IException
. , throw
, . Exception
, . - , .
Exception
, , .
IException
:
class ExcImpl : IException { friend class Exception; const std::string m_What; const int m_Code; ExcImpl(const char* what, int code); ExcImpl(const ExcImpl&) = default; IException* Clone() const override; void Delete() override; protected: ~ExcImpl() = default; public: const char* What() const override; int Code() const override; }; ExcImpl::ExcImpl(const char* what, int code) : m_What(what), m_Code(code) {} IException* ExcImpl::Clone() const { return new ExcImpl(*this); } void ExcImpl::Delete() { delete this; } const char* ExcImpl::What() const { return m_What.c_str(); } int ExcImpl::Code() const { return m_Code; }
Exception
:
Exception::Exception(const char* what, int code) : m_Ptr(new ExcImpl(what, code)) {}
, â .NET â , â , C++/CLI. , , , C++/CLI.
5.2
- :
template <typename T> class ICollect { protected: virtual ~ICollect() = default; public: virtual ICollect<T>* Clone() const = 0; virtual void Delete() = 0; virtual bool IsEmpty() const = 0; virtual int GetCount() const = 0; virtual T& GetItem(int ind) = 0; virtual const T& GetItem(int ind) const = 0; ICollect<T>& operator=(const ICollect<T>&) = delete; };
, -, .
template <typename T> class ICollect; template <typename T> class Iterator; template <typename T> class Contain { typedef ICollect<T> CollType; CollType* m_Coll; public: typedef T value_type; Contain(CollType* coll); ~Contain();
. , . , , , , - begin()
end()
, . (. [Josuttis]), for
. . , , .
6. -
. -, . . , ++. , .NET, Java Pyton. . , , . .NET Framework C++/CLI C++. .
7.
-, .
.
delete
.- .
- .
.
, delete
. , .
- , . , , delete
.
.
, , , , .
Les références
[GoF]
Gamma E., Helm R., Johnson R., Vlissides J. Méthodes de conception orientée objet. ModÚles de conception.: Per. de l'anglais - Saint-Pétersbourg: Peter, 2001.
[Josuttis]
Josattis, Nikolai M. C ++ Standard Library: Reference Guide, 2nd ed.: Per. de l'anglais - M.: LLC «I.D. Williams, 2014.
[Dewhurst]
, . C++. .: . . â .: , 2012.
[Meyers1]
, . C++. 35 .: . . â .: , 2000.
[Meyers2]
, . C++. 55 .: . . â .: , 2014.
[Meyers3]
, . C++: 42 C++11 C++14.: . . â .: «.. », 2016.
[Sutter]
, . C++.: . . â : «.. », 2015.
[Sutter/Alexandrescu]
, . , . ++.: . . â .: «.. », 2015.