Qu'est-ce qu'une classe proxy en C++? Pourquoi est-il créé et où est-il utile?
Un proxy est une classe qui fournit une interface modifiée à une autre classe.
Voici un exemple - supposons que nous ayons une classe de tableau que nous voulons seulement contenir des chiffres binaires (1 ou 0). Voici un premier essai:
struct array1 {
int mArray[10];
int & operator[](int i) {
/// what to put here
}
}; `
Nous voulons operator[]
à jeter si on dit quelque chose comme a[1] = 42
, mais ce n'est pas possible car cet opérateur ne voit que l'index du tableau, pas la valeur stockée.
Nous pouvons résoudre cela en utilisant un proxy:
#include <iostream>
using namespace std;
struct aproxy {
aproxy(int& r) : mPtr(&r) {}
void operator = (int n) {
if (n > 1 || n < 0) {
throw "not binary digit";
}
*mPtr = n;
}
int * mPtr;
};
struct array {
int mArray[10];
aproxy operator[](int i) {
return aproxy(mArray[i]);
}
};
int main() {
try {
array a;
a[0] = 1; // ok
a[0] = 42; // throws exception
}
catch (const char * e) {
cout << e << endl;
}
}
La classe proxy fait maintenant notre recherche d'un chiffre binaire et nous faisons le tableau operator[]
retourne une instance du proxy qui a un accès limité aux internes du tableau.
Une classe proxy en C++ est utilisée pour implémenter le Proxy Pattern dans lequel un objet est une interface ou un médiateur pour un autre objet.
Une utilisation typique d'une classe proxy en C++ implémente l'opérateur [] car l'opérateur [] peut être utilisé pour obtenir des données ou pour définir des données dans un objet. L'idée est de fournir une classe proxy qui permet la détection d'une utilisation de données get de l'opérateur [] par rapport à l'utilisation de données set de l'opérateur []. L'opérateur [] d'une classe utilise l'objet proxy pour aider à faire la bonne chose en détectant si l'opérateur [] est utilisé pour obtenir ou définir des données dans l'objet.
Le compilateur C++ sélectionne les opérateurs et opérateurs de conversion appropriés dans la classe cible fournie et les définitions de classe proxy afin de faire un usage particulier de l'opérateur [].
Cependant, il existe d'autres utilisations pour une classe proxy en C++. Par exemple, consultez cet article sur Objets auto-enregistrés en C++ de Dr. Dobbs qui décrit l'utilisation d'une classe proxy dans le cadre d'une fabrique d'objets. La fabrique d'objets fournit un type d'objet particulier en fonction de certains critères, dans cet exemple un format d'image graphique. Chacun des différents convertisseurs d'images graphiques est représenté par un objet proxy.
Toutes ces exigences peuvent être satisfaites en utilisant un "magasin spécialisé" dans lequel il n'y a pas un seul endroit dans le code au moment de la compilation qui connaît tous les formats pris en charge. La liste des objets pris en charge est créée au moment de l'exécution lorsque chaque objet au format de fichier enregistre son existence avec un objet de magasin spécialisé.
La construction d'un magasin spécialisé comprend quatre parties:
- Chaque classe qui va dans le magasin sera représentée par une classe proxy. Le proxy sait comment créer des objets pour le magasin et fournit une interface standard pour les informations sur la classe.
- Vous devez décider des critères que le magasin spécialisé exposera aux appelants, puis implémenter des interfaces pour ces critères dans le magasin, dans la classe proxy et dans la classe d'origine.
- Toutes les classes proxy dériveront d'une classe de base commune afin que le magasin spécialisé puisse les utiliser de manière interchangeable. Chaque classe proxy sera implémentée comme un modèle qui appelle des fonctions statiques dans la classe d'origine.
- Les classes proxy seront enregistrées automatiquement au démarrage du programme en définissant une variable globale pour chaque classe proxy dont le constructeur enregistrera la classe proxy auprès du magasin spécialisé.
Voir aussi cette réponse, https://stackoverflow.com/a/53253728/146697 , à une question sur les itérateurs C++ dans lesquels une classe proxy est utilisée pour représenter comme objet unique chacun des membres du tableau d'une struct. La structure est une base de données résidant en mémoire pour une application intégrée. Plusieurs types de mnémoniques différents sont stockés sous forme de tableaux de caractères de texte dans la base de données résidente en mémoire. La classe proxy fournit une représentation qui peut ensuite être utilisée avec un itérateur pour parcourir la liste des mnémoniques dans une zone particulière. L'itérateur accède à l'objet proxy via une classe de base et l'intelligence du nombre de mnémoniques que représente l'objet proxy et de la longueur de chaque mnémonique dans l'objet proxy lui-même.
Un autre exemple serait la façon dont les objets Microsoft DCOM (Distributed COM) utilisent un proxy sur la machine hôte d'un utilisateur de l'objet DCOM pour représenter l'objet réel qui réside sur une autre machine hôte. Le proxy fournit une interface pour l'objet réel sur une machine différente et gère la communication entre l'utilisateur de l'objet et l'objet réel.
Pour résumer, un objet proxy est utilisé pour servir d'intermédiaire à l'objet réel. Un objet proxy est utilisé chaque fois qu'il doit y avoir une sorte de conversion ou de transformation entre l'utilisateur d'un objet et l'objet réel avec une sorte d'indirection qui fournit un service permettant l'utilisation de l'objet réel lorsqu'il y a un obstacle à l'utilisation l'objet réel directement.
EDIT - Un exemple simple utilisant un proxy avec l'opérateur [] pour un magasin de données de tableau simple
La source suivante utilise un objet proxy pour l'opérateur [] d'une classe. La sortie du faisceau de test est fournie ci-dessous pour montrer la création et la destruction des divers objets proxy pendant que la classe proxy est utilisée pour accéder et manipuler la classe réelle. Il est instructif de l'exécuter dans un débogueur pour le voir s'exécuter.
// proxy.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <string.h>
#include <iostream>
class TArrayProxy;
// The actual class which we will access using a proxy.
class TArray
{
public:
TArray();
~TArray ();
TArrayProxy operator [] (int iIndex);
int operator = (TArrayProxy &j);
void Dump (void);
char m_TarrayName[4]; // this is the unique name of a particular object.
static char TarrayName[4]; // This is the global used to create unique object names
private:
friend class TArrayProxy; // allow the proxy class access to our data.
int iArray[10]; // a simple integer array for our data store
};
// The proxy class which is used to access the actual class.
class TArrayProxy
{
public:
TArrayProxy(TArray *p = 0, int i=0);
~TArrayProxy();
TArrayProxy & operator = (int i);
TArrayProxy & operator += (int i);
TArrayProxy & operator = (TArrayProxy &src);
operator int ();
int iIndex;
char m_TarrayproxyName[4]; // this is the unique name of a particular object.
static char TarrayproxyName[4]; // This is the global used to create unique object names
private:
TArray *pArray; // pointer to the actual object for which we are a proxy.
};
// initialize the object names so as to generate unique object names.
char TArray::TarrayName[4] = {" AA"};
char TArrayProxy::TarrayproxyName[4] = {" PA"};
// Construct a proxy object for the actual object along with which particular
// element of the actual object data store that this proxy will represent.
TArrayProxy::TArrayProxy(TArray *p /* = 0 */, int i /* = 0 */)
{
if (p && i > 0) {
pArray = p;
iIndex = i;
strcpy (m_TarrayproxyName, TarrayproxyName);
TarrayproxyName[2]++;
std::cout << " Create TArrayProxy " << m_TarrayproxyName << " iIndex = " << iIndex << std::endl;
} else {
throw "TArrayProxy bad p";
}
}
// The destructor is here just so that we can log when it is hit.
TArrayProxy::~TArrayProxy()
{
std::cout << " Destroy TArrayProxy " << m_TarrayproxyName << std::endl;
}
// assign an integer value to a data store element by using the proxy object
// for the particular element of the data store.
TArrayProxy & TArrayProxy::operator = (int i)
{
pArray->iArray[iIndex] = i;
std::cout << " TArrayProxy assign = i " << i << " to " << pArray->m_TarrayName << " using proxy " << m_TarrayproxyName << " iIndex " << iIndex << std::endl;
return *this;
}
TArrayProxy & TArrayProxy::operator += (int i)
{
pArray->iArray[iIndex] += i;
std::cout << " TArrayProxy add assign += i " << i << " to " << pArray->m_TarrayName << " using proxy " << m_TarrayproxyName << " iIndex " << iIndex << std::endl;
return *this;
}
// assign an integer value that is specified by a proxy object to a proxy object for a different element.
TArrayProxy & TArrayProxy::operator = (TArrayProxy &src)
{
pArray->iArray[iIndex] = src.pArray->iArray[src.iIndex];
std::cout << " TArrayProxy assign = src " << src.m_TarrayproxyName << " iIndex " << src.iIndex << " to " << m_TarrayproxyName << " iIndex "<< iIndex << " from" << std::endl;
return *this;
}
TArrayProxy::operator int ()
{
std::cout << " TArrayProxy operator int " << m_TarrayproxyName << " iIndex " << iIndex << " value of " << pArray->iArray[iIndex] << std::endl;
return pArray->iArray[iIndex];
}
TArray::TArray()
{
strcpy (m_TarrayName, TarrayName);
TarrayName[2]++;
std::cout << " Create TArray = " << m_TarrayName << std::endl;
for (int i = 0; i < sizeof(iArray)/sizeof(iArray[0]); i++) { iArray[i] = i; }
}
// The destructor is here just so that we can log when it is hit.
TArray::~TArray()
{
std::cout << " Destroy TArray " << m_TarrayName << std::endl;
}
TArrayProxy TArray::operator [] (int iIndex)
{
std::cout << " TArray operator [" << iIndex << "] " << m_TarrayName << std::endl;
if (iIndex > 0 && iIndex <= sizeof(iArray)/sizeof(iArray[0])) {
// create a proxy object for this particular data store element
return TArrayProxy(this, iIndex);
}
else
throw "Out of range";
}
int TArray::operator = (TArrayProxy &j)
{
std::cout << " TArray operator = " << m_TarrayName << " from" << j.m_TarrayproxyName << " index " << j.iIndex << std::endl;
return j.iIndex;
}
void TArray::Dump (void)
{
std::cout << std::endl << "Dump of " << m_TarrayName << std::endl;
for (int i = 0; i < sizeof(iArray)/sizeof(iArray[0]); i++) {
std::cout << " i = " << i << " value = " << iArray [i] << std::endl;
}
}
// ----------------- Main test harness follows ----------------
// we will output the line of code being hit followed by the log of object actions.
int _tmain(int argc, _TCHAR* argv[])
{
TArray myObj;
std::cout << std::endl << "int ik = myObj[3];" << std::endl;
int ik = myObj[3];
std::cout << std::endl << "myObj[6] = myObj[4] = 40;" << std::endl;
myObj[6] = myObj[4] = 40;
std::cout << std::endl << "myObj[5] = myObj[5];" << std::endl;
myObj[5] = myObj[5];
std::cout << std::endl << "myObj[2] = 32;" << std::endl;
myObj[2] = 32;
std::cout << std::endl << "myObj[8] += 20;" << std::endl;
myObj[8] += 20;
myObj.Dump ();
return 0;
}
Et voici la sortie de cet exemple à partir d'une application console avec Visual Studio 2005.
Create TArray = AA
int ik = myObj[3];
TArray operator [3] AA
Create TArrayProxy PA iIndex = 3
TArrayProxy operator int PA iIndex 3 value of 3
Destroy TArrayProxy PA
myObj[6] = myObj[4] = 40;
TArray operator [4] AA
Create TArrayProxy PB iIndex = 4
TArrayProxy assign = i 40 to AA using proxy PB iIndex 4
TArray operator [6] AA
Create TArrayProxy PC iIndex = 6
TArrayProxy assign = src PB iIndex 4 to PC iIndex 6 from
Destroy TArrayProxy PC
Destroy TArrayProxy PB
myObj[5] = myObj[5];
TArray operator [5] AA
Create TArrayProxy PD iIndex = 5
TArrayProxy operator int PD iIndex 5 value of 5
TArray operator [5] AA
Create TArrayProxy PE iIndex = 5
TArrayProxy assign = i 5 to AA using proxy PE iIndex 5
Destroy TArrayProxy PE
Destroy TArrayProxy PD
myObj[2] = 32;
TArray operator [2] AA
Create TArrayProxy PF iIndex = 2
TArrayProxy assign = i 32 to AA using proxy PF iIndex 2
Destroy TArrayProxy PF
myObj[8] += 20;
TArray operator [8] AA
Create TArrayProxy PG iIndex = 8
TArrayProxy add assign += i 20 to AA using proxy PG iIndex 8
Destroy TArrayProxy PG
Dump of AA
i = 0 value = 0
i = 1 value = 1
i = 2 value = 32
i = 3 value = 3
i = 4 value = 40
i = 5 value = 5
i = 6 value = 40
i = 7 value = 7
i = 8 value = 28
i = 9 value = 9
Un classe proxy vous permet de cacher les données privées d'une classe aux clients de la classe.
Fournir aux clients de votre classe une classe proxy qui ne connaît que l'interface publique de votre classe permet aux clients d'utiliser les services de votre classe sans donner au client l'accès aux détails d'implémentation de votre classe.