Item 43:访问模板基类中的名称

Item 43: Know how to access names in templatized base classes.

从面相对象C++转移到模板C++时,你会发现类继承在某些场合不在好使了。 比如父类模板中的名称对子类模板不是直接可见的,需要通过this->前缀、using或显式地特化模板父类来访问父类中的名称。

因为父类模板在实例化之前其中的名称是否存在确实是不确定的,而C++偏向于早期发现问题(early diagnose),所以它会假设自己对父类完全无知。

编译错的一个例子


一个MsgSender需要给多个Company发送消息,我们希望在编译期进行类型约束,于是选择了模板类来实现MsgSender。

template<typename Company>
class MsgSender{
public:
    void sendClear(const MsgInfo& info){...}    // 发送明文消息
    void sendSecret(const MsgInfo& info){...}   // 发送密文消息
};

由于某种需求我们需要继承MsgSender,比如需要在发送前纪录日志:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        // 存储一些日志
        sendClear(info);    // 编译错!
    }
};

首先要说明这里我们创建了新的方法sendClearMsg而不是直接重写sendClear是一个好的设计, 避免了隐藏父类中的名称,见Item 33;也避免了重写父类的非虚函数,见Item 36

编译错误发生的原因是编译器不知道父类MsgSender中是否有一个sendClear,因为只有当Company确定后父类才可以实例化。 而在解析子类LoggingMsgSender时父类MsgSender还没有实例化,于是这时根本不知道sendClear是否存在。

为了让这个逻辑更加明显,假设我们需要一个公司CompanyZ,由于该公司的业务只能发送密文消息。所以我们特化了MsgSender模板类:

template<>
class MsgSender<CompanyZ>{
public:
    void sendSecret(const MsgInfo& info){...}   // 没有定义sendClear()
};

template<>意味着这不是一个模板类的定义,是一个模板类的全特化(total template specialization)。 我们叫它全特化是因为MsgSender没有其它模板参数,只要CompanyZ确定了MsgSender就可以被实例化了。

现在前面的编译错误就更加明显了:如果MsgSender的模板参数Company == CompanyZ, 那么sendClear()方法是不存在的。这里我们看到在模板C++中继承是不起作用的。

访问模板父类中的名称


既然模板父类中的名称在子类中不是直接可见的,我们来看如何访问这些名称。这里介绍三种办法:

this指针

父类方法的调用语句前加this->:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        ...
        this->sendClear(info);
    }
};

这样编译器会假设sendClear是继承来的。

using 声明

把父类中的名称使用using声明在子类中。该手法我们在Item 33中用过,那里是为了在子类中访问被隐藏的父类名称, 而这里是因为编译器不会主动去搜索父类的作用域。

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    using MsgSender<Company>::sendClear;  
    void sendClearMsg(const MsgInfo& info){
        ...
        sendClear(info);
    }
};

using语句告诉编译器这个名称来自于父类MsgSender。

调用时声明

最后一个办法是在调用时显式指定该函数所在的作用域(父类):

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        ...
        MsgSender<Company>::sendClear(info);
    }
};

这个做法不是很好,因为显式地指定函数所在的作用域会禁用虚函数特性。万一sendClear是个虚函数呢?

子类模板无法访问父类模板中的名称是因为编译器不会搜索父类作用域,上述三个办法都是显式地让编译器去搜索父类作用域。 但如果父类中真的没有sendClear函数(比如模板参数是CompanyZ),在后续的编译中还是会抛出编译错误。

转载地址:http://harttle.land/2015/09/10/effective-cpp-43.html
感谢作者 Harttle

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是代码实现: ```c++ #include<iostream> using namespace std; // 定义抽象类Shape class Shape { public: virtual double calArea() = 0; // 纯虚函数 }; // 定义三角形类Triangle class Triangle : public Shape { public: double base, height; Triangle(double b, double h) { base = b; height = h; } double calArea() { return 0.5 * base * height; } }; // 定义矩形类Rectangle class Rectangle : public Shape { public: double length, width; Rectangle(double l, double w) { length = l; width = w; } double calArea() { return length * width; } }; // 定义正方形类Square class Square : public Shape { public: double side; Square(double s) { side = s; } double calArea() { return side * side; } }; // 定义圆形类Circle class Circle : public Shape { public: double radius; Circle(double r) { radius = r; } double calArea() { return 3.14 * radius * radius; } }; // 定义函数fun,输出图形面积 void fun(Shape* s) { cout << "通过基类指针调用,面积为:" << s->calArea() << endl; } void fun(Shape& s) { cout << "通过基类对象引用调用,面积为:" << s.calArea() << endl; } int main() { // 定义Triangle、Rectangle、Square、Circle类的对象 double b, h, l, w, s, r; cout << "请输入三角形的底和高:" << endl; cin >> b >> h; Triangle tri(b, h); cout << "请输入矩形的长和宽:" << endl; cin >> l >> w; Rectangle rec(l, w); cout << "请输入正方形的边长:" << endl; cin >> s; Square squ(s); cout << "请输入圆形的半径:" << endl; cin >> r; Circle cir(r); // 分别调用fun函数输出各种图形的面积 cout << "计算三角形面积:" << endl; fun(&tri); fun(tri); cout << "计算长方形面积:" << endl; fun(&rec); fun(rec); cout << "计算正方形面积:" << endl; fun(&squ); fun(squ); cout << "计算圆形面积:" << endl; fun(&cir); fun(cir); return 0; } ``` 运行结果: ``` 请输入三角形的底和高: 5 7 请输入矩形的长和宽: 4 5 请输入正方形的边长: 6.7 请输入圆形的半径: 3.4 计算三角形面积: 通过基类指针调用,面积为:17.5 通过基类对象引用调用,面积为:17.5 计算长方形面积: 通过基类指针调用,面积为:20 通过基类对象引用调用,面积为:20 计算正方形面积: 通过基类指针调用,面积为:44.89 通过基类对象引用调用,面积为:44.89 计算圆形面积: 通过基类指针调用,面积为:36.3168 通过基类对象引用调用,面积为:36.3168 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值