What is Proxy Class in C++

Solution 1:

A proxy is a class that provides a modified interface to another class.

Here is an example - suppose we have an array class that we only want to contain binary digits (1 or 0). Here is a first try:

struct array1 {
    int mArray[10];
    int & operator[](int i) {
      /// what to put here
    }
}; `

We want operator[] to throw if we say something like a[1] = 42, but that isn't possible because that operator only sees the index of the array, not the value being stored.

We can solve this using a 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;
    }
}

The proxy class now does our checking for a binary digit and we make the array's operator[] return an instance of the proxy which has limited access to the array's internals.

Solution 2:

A proxy class in C++ is used to implement the Proxy Pattern in which an object is an interface or a mediator for some other object.

A typical use of a proxy class in C++ is implementing the [] operator since the [] operator may be used to get data or to set data within an object. The idea is to provide a proxy class which allows for the detection of a get data use of the [] operator versus the set data use of the [] operator. The [] operator of a class uses the proxy object to assist in doing the right thing by detecting whether the [] operator is being used to get or set data in the object.

The C++ compiler selects the appropriate operators and conversion operators from the provided target class and the proxy class definitions in order to make a particular use of the [] operator work.

However there are other uses for a proxy class in C++. For instance see this article on Self-Registering Objects in C++ from Dr. Dobbs that describes using a proxy class as part of an object factory. The object factory provides a particular type of object depending on some criteria, in this example a graphics image format. Each of the different graphic image converters is represented by a proxy object.

All of these requirements can be met by using a "specialty store" in which there is no single place in the code at compile time that knows about all supported formats. The list of supported objects is built at run time when each file-format object registers its existence with a specialty-store object.

There are four parts to building a specialty store:

  • Each class that goes in the store will be represented by a proxy class. The proxy knows how to create objects for the store and provides a standard interface for information about the class.
  • You must decide what criteria the specialty store will expose to callers, then implement interfaces for those criteria in the store, in the proxy class, and in the original class.
  • All proxy classes will derive from a common base class so that the specialty store can use them interchangeably. Each proxy class will be implemented as a template that calls static functions in the original class.
  • Proxy classes will be registered automatically at program startup by defining a global variable for each proxy class whose constructor will register the proxy class with the specialty store.

See also this answer, https://stackoverflow.com/a/53253728/1466970, to a question about C++ iterators in which a proxy class is used to represent as a unique object each of the array members of a struct. The struct is a memory resident database for an embedded application. Several different kinds of mnemonics are stored as text character arrays in the memory resident database. The proxy class provides a representation which can then be used with an iterator to traverse the list of mnemonics in a particular area. The iterator accesses the proxy object through a base class and the intelligence as to the how many mnemonics the proxy object represents and the length of each mnemonic is in the proxy object itself.

Another example would be how Microsoft DCOM (Distributed COM) objects use a proxy on the host machine of a user of the DCOM object to represent the actual object which resides on another host machine. The proxy provides an interface for the actual object on a different machine and handles the communication between the user of the object and the actual object.

To sum up, a proxy object is used to act as an intermediary to the actual object. A proxy object is used when ever there needs to be some kind of conversion or transformation between the user of an object and the actual object with some kind of indirection which provides a service allowing the use of the actual object when there is some obstacle in using the actual object directly.

EDIT - A simple example using a proxy with operator [] for a simple array data store

The following source uses a proxy object for the operator[] of a class. The output of the test harness is provided below to show the creation and destruction of the various proxy objects as the proxy class is used to access and manipulate the actual class. It is instructive to run this in a debugger to watch it execute.

// 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;
}

And here is the output of this example from a console application with 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

Solution 3:

A proxy class allows you to hide the private data of a class from clients of the class.

Providing clients of your class with a proxy class that knows only the public interface to your class enables the clients to use your class's services without giving the client access to your class's implementation details.