难度:
rel="File-List" href="file:///C:%5CUsers%5CADMINI%7E1%5CAppData%5CLocal%5CTemp%5Cmsohtml1%5C10%5Cclip_filelist.xml">
知识回顾:
两个类在什么情况下是“is-a”关系?
class B
{
public:
void foo();
};
class D: public B
{};
D 公有继承自 B,那么D和B就是“is-a”关系,且D is-a B。下面的这个代码能够很好的体现出这种关系。
D d;
d.foo();
foo其实是B::foo。那么用对象d来调用这个成员函数时,由于d的类型D是is-a B,那么B::foo的this指针就由对象d的地址转换而来。d.foo()这样的调用就顺理成章地通过编译。
问题:
现在项目经理给你提出了一个要求叫你提供一个容器类,实现保存任意类的对象指针和该类的无返回值无参数非cv-qualified的成员函数地址,并提供一个接口以调用里面所有的成员函数。你该怎么实现呢?只要学过模板,这个问题应该不难,稍加片刻就能得出答案。
rel="File-List" href="file:///C:%5CUsers%5CADMINI%7E1%5CAppData%5CLocal%5CTemp%5Cmsohtml1%5C02%5Cclip_filelist.xml">
class any_mf
{
public:
virtual ~any_mf();
virtual void fire() = 0;
};
template<typename T>
class mf_holder: public any_mf
{
public:
mf_holder(T& object, void(T::*mf)())
:object_(object), mf_(mf)
{}
virtual void fire() //重写any_mf::fire
{
(object_.*mf_)();
}
private:
T& object_;
void (T::*mf_)();
};
class container
{
public:
//其他函数在此省略
template<typename T>
void save(T& object, void(T::*mf)())
{
any_mf * amf = new mf_holder<T>(object, mf);
try
{
cont_.push_back(amf);
}
catch(std::bad_alloc&)
{
delete amf;
}
}
void fire()
{
std::for_each(cont_.begin(), cont_.end(), *this);
}
void operator()(any_mf* amf)
{
if(amf)
amf->fire();
}
private:
std::vector<any_mf*> cont_;
};
思考一下container::save会有什么问题?
答案:
这个container对下面的代码无法通过编译。这里使用上面代码中的class B和class D
D d;
container cont;
cont.save(d, &D::foo);
因为无法精确匹配save的模板参数T,从第一个参数推导,可得到T为class D,而从第二个参数推导,T则为class B。为什么会导致这样呢?因为D中并没有定义名为foo的成员函数。普遍认为D继承自B,那么foo也应该被D继承下来,的确是这样,因为名字查找规则允许D::foo这样的写法,但是foo在这里并不是D的,而是B。函数模板参数的推导是精确匹配的,在推导过程中,是不能将void (B::*)()转换为void(D::*)()的。那么怎么解决这个问题呢?显式指定模板参数。
cont.save<D>(d, &D::foo);
当然,这种方法太落后,我们可以让再加一个模板参数,运用点模板的技巧来解决此问题。
template<typename T, typename Abstract>
void save(T& object, void (Abstract::*mf)())
{
if(is_a<T, Abstract>::value)
cont_.push_back(new mf_holder<T>(object, mf));
//这里用该用类的特化。而不是简单的if,在此省略具体做法
}
这是一个很简单的问题,但又很容易被忽略掉。