Last Updated:

Programming with interfaces in C++

Using interfaces makes it easier to program in managed C++. Interfaces are implemented through classes, and you can cast a pointer to a class to get a pointer to an interface. Interface methods can be called using both class pointers and interface pointers; however, in order to take full advantage of polymorphism, it is preferable to use pointers to interfaces wherever possible.

Implementing Interfaces

In C++, indicating that a class implements an interface is done by using a colon, which is also used to indicate the inheritance of the class. A managed class can inherit from a single managed class and, in addition, from one or more managed interfaces. In this case, the base class should be listed first, just after the colon. Note that, unlike managed interfaces, inheriting managed classes can only be publicly available.

_gc class HotelBroker: public Broker, public IHotellInfo,
// garbage collector class - HotelBroker: public Broker,
public IHotelAdmin, public IHotelReservation
{

};

In this example, the HotelBroker class derives from the Broker class and implements the IHotellnfoIHotelAdmin, and IHotelReservation interfaces. HotelBroker must implement all the methods of these interfaces, either directly or using an implementation inherited from the Broker base class.

A detailed example of the use of interfaces will be discussed in this chapter a little later, when we will take up the implementation of the second step of the system being created.

And now, as a small example of the above, consider the Smalllnterface program. The Account class implements the IBasicAccount interface. The description of this interface demonstrates the syntax for declaring an interface property.

//Account.h
_gc_interface IBasicAccount
// garbage collector - IBasicAccount
{
void Deposit(Decimal amount); // Deposit (Decimal
// amount);
void Withdraw(Decimal amount); // Remove(Decimal
// amount);
_property Decimal get_Balance(); // Decimal number };
_gc class Account: public IBasicAccount
// garbage collector - Account class: IBasicAccount
{
private: // private
Decimal balance; // Decimal balance public:
Account(Decimal balance) // Account (Decimal balance)
{
this › balance = balance; // balance
}
void Deposit(Decimal amount) // Deposit (Decimal amount)
{
balance = balance + amount; // balance = balance + quantity
}
void Withdraw(Decimal amount) // Withdraw (Decimal amount)
{
balance = balance - amount; // balance = balance - quantity
}
_property Decimal get_Balance() // Decimal number
{
return balance; // balance
}
};

Using Interfaces

When you know that a class supports a particular interface, its methods can be called by using a pointer to an instance of the class. If you do not know whether an interface is implemented by a class, you can try casting a pointer to the class to a pointer to an interface. And if the class does not support this interface, an exception will be thrown when such an attempt is made. The following example, taken from the Smalllnterfase.h file, demonstrates just such a validation method.

try // attempt
{
IBasicAccount *pifc2 =
dynamic_cast<IBasicAccount *>(pacc2);
pifc2>Deposit(25); // Deposit
Console::WriteLine(
"balance = {0}", _box(pifc2 › Balance)); // Balance
}
catch (NullReferenceException *pe)
{
Console::WriteLine(
"IBasicAccount is not supported"); // IBasicAccount
// not supported
Console::WriteLine(pe › Message); // Message
}
}

Small Interface uses two almost identical classes. The Account class supports the IBasicAccount interface, and the second class, NoAccount, does not support it. Both classes have identical sets of methods and properties. Here is the full contents of the Smalllnterfase.srp and Smalllnterfase.h files. Note that this program attempts to point pointers to instances of the Account and NoAccount classes to a pointer to the IBasicAccount interface.

//Smallnterfase.cpp
fusing <mscorlib.dll>
using namespace System;
// use namespace System;
#include "Account.h"
#include "NoAccount.h"
#include "Smallinterface.h"
void main() // main
{
SmallInterface::Main(); // Main
}
//Smallinterface.h
_gc class SmallInterface
// garbage collector class SmallInterface
{
public:
static void Main() // Main
{
Account *pacc - new Account(100); // new Account
// Use class reference
Console::WriteLine(
"balance = {0}", _box(pacc › Balance)); // Balance
paccDeposit(25); // Deposit
Console::WriteLine(
"balance = {0}", _box(pacc › Balance)); // Balance
// Use interface reference
IBasicAccount *pifc =
dynamic_cast<IBasicAccount *>(pacc); pifcDeposit(25); // Deposit
Console::WriteLine(
"balance = {0}", _box(pifc › Balance)); // Balance
// Now try with the class,
// non-implementing
IBasicAccount NoAccount *pacc2 = new NoAccount(500);
// Use class reference
Console::WriteLine(
"balance = {0}", _box(pacc2 › Balance)); // Balance
pacc2>Deposit(25); // Deposit
Console::WriteLine(
"balance = {0}", _box(pacc2 › Balance)); // Balance
// Try interface pointer try
// try
{
IBasicAccount *piba=
dynamic_cast<IBasicAccount *>(pacc2);
piba › Deposit(25); // Deposit
Console::WriteLine(
"balance = {0}", _box(piba › Balance)); // Balance
}
catch (NullReferenceException *pe)
{
Console::WriteLine(
"IBasicAccount is not supported"); // IBasicAccount
//not supported
Console::WriteLine(pe › Message); // Message
}
}
};

In this example, we first deal with the Account class, which supports the IBasicAccount interface. In this case, attempts to call interface methods using both a pointer to the class and a pointer to an interface resulting from casting succeed. Next, we deal with the NoAccount class. Although the set of methods of this class is identical to the set of methods of the Account class, its description does not specify that it implements the IBasicAccount interface.

//No Account.h
_gc class No Account
// garbage collector class No Account
{

When you run this program, a NullReferenceException is thrown. This occurs when you try to use a pointer to the IBasicAccount interface that results from dynamically casting a pointer to the NoAccount class. (In other words, an exception is thrown when you try to cast noAccount * to pointer type data on the IBasicAccount* interface.) If we were to use normal C-style casting, an InvalidCastException would be thrown in such an attempt. However, when compiling such a program, a warning would be issued that the use of C-style casting is not recommended.

balance = 100
balance = 125
balance = 150
balance = 500
balance = 525
IBasicAccount is not supported
Value null was found where an instance of an object was required.
Here is the translation of the issue:
balance = 100
balance =125
balance = 150
balance = 500
balance = 525
IBasicAccount not supported
A null pointer where a pointer to an object is required.

Dynamic use of interfaces

A useful feature of interfaces is the ability to use them in dynamic scenarios, which allows you to check whether the interface is supported by a certain class as the program runs. If the interface is supported, we can take advantage of the features provided by it; otherwise, the program may ignore the interface. In fact, this dynamic behavior is implemented by handling the exceptions that occur, as demonstrated above. Although this approach is quite workable, it is somewhat clumsy and leads to the appearance of hard-to-read programs. C++ supports the use of the dynamic_cast and typeid operators, and the .NET Framework has a Type class that facilitates the dynamic use of interfaces.

As an example, consider the ICustomer2 interface, which has, compared to the ICustomer 1 interface, an additional ShowCustomer method.

_gc_interface ICustomer2: ICustomerl
// garbage collector - ICustomer2: ICustomerl
{
public:
void ShowCustomers(int id); // identifier
};

Suppose the Customerl class supports the ICustomer1 interface, and the Customer2 class supports the ICustomer2 interface. For a console client program, it is more convenient to use the original ShowCustomer method rather than the GetCustomer method, because the latter creates a list of arrays and copies data to it. Therefore, the client program will prefer to work with the ICustomer2 interface, if possible. The TestlnterfaceBeforeCast folder contains the program discussed in the next section.

To check interface support before casting types

You can test interface support by dynamically casting the pointer type and handling an exception that may occur. However, a more elegant solution is to perform a pre-type check, while avoiding exceptions. If the object supports the required interface, you can perform type casting to gain access to the interface. C# supports the use of the user-friendly is operator to check whether an object supports a particular interface. Unfortunately, in C++, you have to use the reflection method implemented through the GetType and Getlnterfase methods to do this. Because this results in a somewhat cumbersome expression, the following example uses the #define directive to define the IS macro (THIS, THAT_INTERFACE), which is used further in the two conditional if statements.

//TestInterfaceBeforeCast.cpp
//MACRO: pObj › GetType() › GetInterface("Somelnterface")!=0
// MACRO
#define IS(THIS, THAT_INTERFACE) (THIS › GetType() › GetInterface(
THAT_INTERFACE)!=0)
#using <mscorlib.dll>
using namespace System;
// use namespace System;
_gc _interface ICustomer1 {};
// garbage collector - ICustomer1;
_gc_interface ICustomer2: ICustomer1
// garbage collector - ICustomer2: ICustomer1
{
public:
void ShowCustomers(int id); // identifier
};
_gc class Customer!: public ICustomer1 {};
// garbage collector class Customerl: ICustomerl {};
_gc class Customer2: public ICustomer2
// garbage collector class Customer2: ICustomer2
{
public:
void ShowCustomers(int id) // identifier
{
Console::WriteLine("Customer2::ShowCustomers:
succeeded");
}
};
void main(void) // main
{
Customerl *pCustl = new Customerl; // not to ICustomer2
Console::WriteLine(pCustl › GetType());
// check if it is of type ICustomer2 before casting
if (IS(pCustl, "ICustomer2"))
{
ICustomer2 *plcust2 =
dynamic_cast<ICustomer2 *>(pCustl);
plcust2 › ShowCustomers(-1);
}
else
Console::WriteLine
("pCustl does not support ICustomer2 interface");
// ("pCustl does not support the ICustomer2 interface");
Customer2 *pCust2 = new Customer2; // yes, on ICustomer2
Console::WriteLine(pCust2 › GetType());
// check if it is of type ICustomer2 before casting
if (IS(pCust2, "ICustomer2"))
{
ICustomer2 *plcust2 =
dynamic_cast<ICustomer2 *>(pCust2);
pIcust2 › ShowCustomers(-1);
}
else
Console::WriteLine
("pCust2 does not support ICustomer2 interface");
// ("pCust2 does not support the ICustomer2 interface");
}

This example demonstrates a far from optimal solution because type checking is performed twice. The first time is when using reflection to test interface support in an IS macro. And once again, the check is performed automatically - when performing dynamic type conversion, in this case, if the interface is not supported, an exception is thrown. The result of the program is shown below. Note that there really is no exception when the program runs.

Customer1
pCustl does not support ICustomer2 interface
Customer2
Customer2::ShowCustomers: succeeded

And here is the translation:

Customer1
pCustl does not support ICustomer2 interface
Customer2
Customer2::ShowCustomers: Successful

dynamic_cast operator

 

The result of executing the dynamic_cast statement is the pointer to the interface itself. If the interface is not supported, the pointer value is set to zero. We use this fact to create a program in which the interface support is checked once. The following snippet is taken from the CastThenTestForNull example, which differs from the previous one, TestlnterfaceBeforeCast, in that it checks for equality to zero of the dynamic casting result.

void main(void) // main
{
Customerl *pCustl = new Customer!; // no ICustomer2
Console::WriteLine(pCustl › GetType());
// Use C++ dynamic_cast operator to check
// presence of ICustomer2
ICustomer2 *plcust2 =
dynamic_cast<ICustomer2 *>(pCustl);
if(plcust2!=0)
p!cust2 › ShowCustomers(-1);
else
Console::WriteLine
("pCustl does not support ICustomer2 interface");
// ("pCustl does not support the ICustomer2 interface");
Customer2 *pCust2 = new Customer2; // yes, there is ICustomer2
Console::WriteLine(pCust2 › GetType());
// Use C++ dynamic_cast operator to check
// presence of ICustomer2
plcust2=
dynamic_cast<ICustomer2 *>(pCust2);
if(plcust2!=0)
plcust2 › ShowCustomers(-1);
else
Console::WriteLine
("pCust2 does not support ICustomer2 interface");
// ("pCust2 does not support the ICustomer2 interface");
}

The result of the castThenTestForNull program shows that indeed, no exception is thrown, but the interface support is checked only once for each of the objects.

Customer1
pCustl does not support ICustomer2 interface
Customer2
ICustomer2::ShowCustomers: succeeded

Here is a translation of this issue:

Customer1
pCustl does not support ICustomer2 interface
Customer2
ICustomer2::ShowCustomers: Successful

If you are familiar with the Microsoft Component Object Model (COM), you should be familiar with checking that the class supports the interface.

Interfaces in Managed C++ and .NET

.NET and the Microsoft Component Object Model (COM) have many similarities. In both of these technologies, the concept of interfaces plays a fundamental role. They are convenient to use to define contracts. The interfaces provide a very dynamic programming style.

In the Microsoft Component Object Model (COM), the developer has to carefully create the infrastructure necessary to implement COM components. To create COM objects, you need to implement a class factory. To dynamically test support for the developer interface (Must implement the Querylnterf method in the Unknown interface; in addition, the AddRef and Release methods should be implemented for appropriate memory management).

When using the same .NET languages, all these actions are performed automatically by a virtual machine that implements the Common Language Runtime (CLR). To create an object, simply use the new operator. Checking that a class supports an interface and getting a pointer to an interface is easy to do using the dynamic_cast operator. Memory management is taken over by the garbage collector.