The Problem
Suppose you need to use a legacy C++ class designed in COM style. Some methods of the class return HRESULT
, and take a pointer as a parameter that could point to an int
, a short
, a std::string
, an enum, and so on. For example:
HRESULT GetName(std::string* name);
Solution 1
The most obvious way of calling this method is:
std::string name;
if (SUCCEEDED(obj->GetName(&name)))
m_name = name;
else
m_name = "Untitled";
If you call the methods over and over again using this way, you will create quite a bit of duplicated code.
Solution 2
One way to solve this is to provide a helper function for the methods that have the same parameter type. For example, for the methods that take int*
as the parameter, you could write a helper function, something like follows:
int GetInt(MyClass* obj, HRESULT (MyClass::*func)(int*), int defaultValue)
{
int i = defaultValue;
if (SUCCEEDED(obj->*func(&i))) return i;
else
return defaultValue;
}
The obj
parameter points to a MyClass
object on which the member method is to be called; the func
parameter points to a member method of MyClass
to be called; the defaultValue
parameter is to be returned if the function call to the member method fails. The helper function hides the details of checking the HRESULT
returned from the member method.
You can now use the helper function when you need to call a method on a MyClass
object, e.g.:
int length = GetInt(obj, &MyClass::GetLength, 0);
The code for calling the GetName()
method can be reduced to:
m_name = GetString(obj, &MyClass::GetName, "Untitled");
The code becomes much more concise. However, there is a problem: you need to write a helper function for each group of methods of each class, where each group of methods has the same parameter type. Let’s say, you have five classes and all of them have methods that use 10 different parameter types, then you have to create 50 helper functions, which is quite a bit of (seemingly duplicated) code.
A Better Solution
Is there a better solution? Yes, by using a combination of template and member function pointers. We can define a template Functor class PropertyGetter
:
template<typename T, typename ARG>
class PropertyGetter
{
T* m_object; HRESULT (T::*m_func)(ARG*);
public:
PropertyGetter(T* obj, HRESULT (T::*func)(ARG*))
: m_object(obj), m_func(func)
{
}
ARG operator()(ARG defaultValue)
{
ARG a = defaultValue;
if (SUCCEEDED((m_object->*m_func)(&a))) {
return a;
}
else
{
return defaultValue;
}
}
};
The template parameter T
represents a class whose methods you want to call, and ARG
represents the type of the parameter of a method. The constructor takes two parameters: a pointer to a class instance and a pointer to a member method. The operator()
calls the target method on the class instance. If the call succeeds, it returns the value returned from the method; otherwise, it returns the defaultValue
passed in.
With this template class, you can call any method of any class, as long as the method returns HRESULT
and takes a pointer as the parameter. The GetName()
and GetLength()
methods can now be called as:
m_name = PropertyGetter<MyClass, std::string>(obj,
&MyClass::GetName)("Untitled");
int length = PropertyGetter<MyClass, int>(obj, &MyClass::GetLength)(0);
which can be conceptually translated into:
{
PropertyGetter<MyClass, std::string> temp1(obj, &MyClass::GetName);
m_name = temp1("Untitled");
}
int length;
{
PropertyGetter<MyClass, int> temp2(obj, &MyClass:GetLength);
length = temp2(0);
}
As you can see in this example, when used properly, the combination of template and function pointers can help you make your code more concise.