仿函数和成员函数指针
一篇关于使用 C++ 模板函子和成员函数指针的文章。
问题
假设你需要使用一个以 COM 风格设计的遗留 C++ 类。该类的一些方法返回 HRESULT
,并接受一个参数是指针,该指针可以指向一个 int
,一个 short
,一个 std::string
,一个枚举类型,等等。例如
HRESULT GetName(std::string* name);
解决方案 1
调用这些方法的显而易见的方式是
std::string name;
if (SUCCEEDED(obj->GetName(&name)))
m_name = name;
else
m_name = "Untitled";
如果你一遍又一遍地使用这种方式调用这些方法,你会产生相当多的重复代码。
解决方案 2
一种解决方法是为具有相同参数类型的方法提供一个辅助函数。例如,对于接受 int*
作为参数的方法,你可以编写一个辅助函数,如下所示
int GetInt(MyClass* obj, HRESULT (MyClass::*func)(int*), int defaultValue)
{
int i = defaultValue;
if (SUCCEEDED(obj->*func(&i))) // call the member method
return i;
else
return defaultValue;
}
obj
参数指向一个 MyClass
对象,成员方法将在其上被调用;func
参数指向 MyClass
的一个成员方法,将被调用;defaultValue
参数是在成员方法调用失败时要返回的值。辅助函数隐藏了检查成员方法返回的 HRESULT
的细节。
现在,当你需要调用 MyClass
对象上的一个方法时,你可以使用辅助函数,例如:
int length = GetInt(obj, &MyClass::GetLength, 0);
调用 GetName()
方法的代码可以简化为
m_name = GetString(obj, &MyClass::GetName, "Untitled");
代码变得更加简洁。但是,存在一个问题:你需要为每个类中具有相同参数类型的方法组编写一个辅助函数。假设你有五个类,并且它们都具有使用 10 种不同参数类型的方法,那么你必须创建 50 个辅助函数,这相当多的(看似重复的)代码。
更好的解决方案
有没有更好的解决方案?是的,通过结合模板和成员函数指针。我们可以定义一个模板函子类 PropertyGetter
///////////////////////////////////////////////////////////////////////////////
// A Functor for calling member methods that returns HRESULT and takes a
// pointer as the parameter, e.g. HRESULT MyClass::GetLength(int* length)
//
// T represents a class whose method is to be called.
// ARG represents the type of the parameter of the method to be called.
///////////////////////////////////////////////////////////////////////////////
template<typename T, typename ARG>
class PropertyGetter
{
T* m_object; // The object on which the method is to be called
HRESULT (T::*m_func)(ARG*); // The member method to be called
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))) // call the member method
{
return a;
}
else
{
return defaultValue;
}
}
};
模板参数 T
表示你想要调用其方法的类,而 ARG
表示方法参数的类型。构造函数接受两个参数:一个类实例的指针和一个成员方法的指针。operator()
在类实例上调用目标方法。如果调用成功,它将返回方法返回的值;否则,它将返回传入的 defaultValue
。
有了这个模板类,你可以调用任何类的任何方法,只要该方法返回 HRESULT
并接受一个指针作为参数。现在可以这样调用 GetName()
和 GetLength()
方法:
m_name = PropertyGetter<MyClass, std::string>(obj,
&MyClass::GetName)("Untitled");
int length = PropertyGetter<MyClass, int>(obj, &MyClass::GetLength)(0);
这可以概念性地转换为
{
PropertyGetter<MyClass, std::string> temp1(obj, &MyClass::GetName);
m_name = temp1("Untitled");
}
int length;
{
PropertyGetter<MyClass, int> temp2(obj, &MyClass:GetLength);
length = temp2(0);
}
如你在此示例中所见,如果使用得当,模板和函数指针的结合可以帮助你使代码更简洁。