【Effective C++】6. 继承与面向对象设计

Item32 确定你的public继承关系建模出is-a的关系

public继承的含义:

  • is-a:student是一种person,person的范围更大
  • 在函数传参的过程,eta能同时接受person和student两种变量,反之不能
class Person {};
class Student : public Person {};

void eta(const Person& p) {}
void study(const Student& s) {}

int main() {
    Person p;
    Student s;
    eta(p);
    eta(s);
    study(s);
    study(p);
    return 0;
}

/*
 * 报错信息
main.cpp:11:11: error: invalid initialization of reference of type ‘const Student&’ from expression of type ‘Person’
   11 |     study(p);
      |           ^
In file included from main.cpp:3:
item_32.h:14:27: note: in passing argument 1 of ‘void study(const Student&)’
   14 | void study(const Student& s) {}
*/

通过public继承建模,应该慎重思考父类和子类的关系,考虑下面两个例子

  • 企鹅是一种鸟,但是不会飞,建模方式1不太恰当
  • 如果方式2不需要区分会飞和不会飞,建模2显得多余
  • 方式3将编译器能够限制的错误推迟到了运行期,同样不恰当
  • 方式4不定义这样的方法,如果子类觉得有必要在定义,有更广的应用范围
// 建模方式1
class Bird {
public:
    virtual void fly() {}
};
class Penguin : public Bird {};

// 建模方式2
class Bird {};
class FlyingBird : public Bird {
public:
    virtual void fly() {}
};
class Penguin : public Bird {};

// 建模方式3
class Bird {
public:
    virtual void fly() {}
};
class Penguin : public Bird {
public:
    virtual void fly() {  error(); }
};

// 建模方式4
class Bird {};
class Penguin : public Bird {};
  •  如果是正方形继承自长方形,可以看出结果不符合预期
class Rectangle {
public:
    virtual void setHeight(int newHeight);
    virtual void setWidth(int newWidth);
    virtual int height() const;
    virtual int width() const;
};
class Square : public Rectangle {};
void makeBigger(Rectangle& r) {
    int oldHeight = r.height();
    r.setHeight(r.width() + 10);
    assert(r.height() == oldHeight);
}

Square s;
makeBigger(s); // 结果不符合预期

Item34 区分接口继承和实现继承

public继承由两部分组成:接口继承和实现继承

  • 声明纯虚函数:子类只继承函数接口
  • 声明虚函数:子类继承函数接口,和一份缺省的代码实现
  • 声明非虚函数:子类继承函数接口,并且不允许重写;

tips:82法则

  • 程序有80%的时间花在20%的代码上
  • 意味着平均函数调用可以有80%的虚函数而不影响效率

声明虚函数子类同时继承了接口和缺省的实现,可能存在危险,考虑以下场景:

  • 航空公司买入了新的C型飞机,由于失误继承了缺省实现,出现不符合预期行为
class Airport {};
class Airplane {
public:
    virtual void fly(const Airport& destination) {
        // 飞机飞到指定目的地
    }
};
// A和B飞行方式相同
class ModelA : public Airplane {};
class ModelB : public Airplane {};

// C型和A和B飞行方式不同
class ModelC : public Airplane {
public:
    // 这里应该实现一种新飞行方式
};
  • 需要切断同时继承接口和缺省实现的行为,将fly继承为纯虚函数,并提供默认的方法
    • 纯虚函数意味着必须由自己的实现;
    • 默认的实现意味着有相同行为的子类可以重复调用;
class Airport {};
class Airplane {
public:
    virtual void fly(const Airport& destination) = 0;
};
void Airplane::fly(const Airport& destination) {
    // 飞机飞到指定目的地
}

// A和B飞行方式相同
class ModelA : public Airplane {
public:
    virtual void fly(const Airport& destination) {
        Airplane::fly(destination);
    }
};
class ModelB : public Airplane {
public:
    virtual void fly(const Airport& destination) {
        Airplane::fly(destination);
    }
};
// C型和A和B飞行方式不同
class ModelC : public Airplane {
public:
    virtual void fly(const Airport& destination) {
        // 这里应该实现一种新飞行方式
    }
};

Item36 绝不重新定义继承来的non-virtual函数

  • 非虚函数的成员函数都是静态绑定
    • 通过pB查找函数,只能找到B作用域内的函数;
    • 通过pD查找函数,由于D内的同名函数覆盖了B的函数,所以只能找到D内的重名版本;
class B {
public:
    void mf() {
        std::cout << "B::mf()\n";
    }
};
class D : public B {
public:
    void mf() {
        std::cout << "D::mf()\n";
    }
};

int main() {
    D x;
    B* pB = &x;
    pB->mf();

    D* pD = &x;
    pD->mf();
    return 0;
}

// 输出
// B::mf()
// D::mf()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨主任o_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值