Effective C++学习笔记(第六章)

2022-05-01  本文已影响0人  crazyhank
条款32:确定public继承塑模出is-a关系

is-ahas-a是C++类的两个重要关系描述,如果类D基于public方式继承于类B,则D类的实例 is-a B类的实例,适用于Base class身上的每一件事情也一定适用于Derived class身上。

条款33:避免遮掩继承而来的名称

C++编译器在编译的时候针对类内的成员函数也会按照变量域类似的规则进行搜索:即先在本类的作用域中搜索,如果找到对应的符号,则不会继续扩大搜索域,如果没有找到则会扩大搜索域,先扩展至其父类域,依次往上扩展。考察下面的示例代码:

#include <iostream>
class B {
public:
    void mf1() {}
    void mf2() {}
};
class D : public B {
public:
    void mf3() {}
    void mf1(int a) {}
};

int main()
{
    D obj;

    obj.mf3(); // 编译OK,调用D::mf3()
    obj.mf1(2); // 编译OK,调用D::mf1(int)
    obj.mf1(); // 编译不通过
    obj.mf2(); // 编译通过,调用B::mf2

    return 0;
}

结论:在子类中需要避免定义与父类中同名的函数(虚函数除外),因为那样的话,子类对象实例就不能调用父类定义的该函数了,如果一定要调用,则需要在子类中使用using语句显式的声明父类被掩盖的函数,如下所示:

#include <iostream>
class B {
public:
    void mf1() {}
    void mf2() {}
};
class D : public B {
public:
    using B::mf1;  // 使用using语句声明父类中被掩盖的函数
    void mf3() {}
    void mf1(int a) {}
};

int main()
{
    D obj;

    obj.mf3(); // 编译OK,调用D::mf3()
    obj.mf1(2); // 编译OK,调用D::mf1(int)
    obj.mf1(); // 现在编译可以通过了
    obj.B::mf1(); // 等同于上面的写法
    obj.mf2(); // 编译通过,调用B::mf2

    return 0;
}
条款34:区分接口继承和实现继承
条款35:考虑virtual函数以外的其它选择
#include <iostream>
class B {
public:
    void FuncWrapper() {std::cout << "Entering B::FooWrapper" << std::endl; Foo();}
private:
    virtual void Foo() {std::cout << "Entering B::Foo" << std::endl;};
};

class D : public B {
private:
    virtual void Foo() {std::cout << "Entering D::Foo" << std::endl;}
};

int main()
{
    D obj;
    obj.FuncWrapper();
    return 0;
}

在基类B中定义了一个非虚函数FuncWrapper,然后定义了一个一般虚函数Foo,在FuncWrapper调用Foo,这样我们将Foo放在private域中,增强了类的封装性。

#include <iostream>
#include <functional>
static void Func1()
{
    std::cout << "Calling Func1" << std::endl;
}
static void Func2()
{
    std::cout << "Calling Func2" << std::endl;
}

using MyFunc = std::function<void()>;  // 使用using语句代替传统的typedef,定义一个函数类型
class MyClass {
public:
    MyClass(MyFunc func) : f(func) {}
    void DoSomething() {f();}
private:
    MyFunc f;
};

int main()
{
    MyClass obj1(Func1), obj2(Func2);

    obj1.DoSomething();
    obj2.DoSomething();

    return 0;
}

在这个MyClass类中,定义了一个私有成员变量,它代表处理函数真正的执行动作,由于每个对象可能的执行动作不一样,所以可以在构造函数中传入具体的函数,注意:这里使用了C++11之后的std::function模板,这是比传统的函数指针更好用的一种C++方式。

条款36:绝不重新定义继承而来的非虚函数

一句话:子类切勿重新定义继承自父类中的非虚函数,如果你真的要这么做,请将其定义成虚函数。

条款37:绝不重新定义继承而来的缺省参数值

这是个C++考试中常考的一个知识点,考察以下示例代码:

#include <iostream>

class B {
public:
    virtual void Foo(int val = 1) {std::cout << "val = " << val << std::endl;}
};
class D : public B {
public:
    virtual void Foo(int val = 2) {std::cout << "val = " << val << std::endl;}
};

int main()
{
    B* p = new D();
    p->Foo();
    return 0;
}

可以看到在调用子类的Foo虚函数版本中,参数却是用了父类中的缺省值。
简而言之:缺省参数的设置是静态的,而虚函数的执行是动态的,所以请不要重新定义继承而来的缺省值,否则会引起误解。

条款38:通过组合关系塑模出“has-a”
条款39:明智而谨慎的使用private继承
条款40:多重继承

以上两条不常用,也不推荐使用。

上一篇 下一篇

猜你喜欢

热点阅读