zoukankan      html  css  js  c++  java
  • 【effective c++读书笔记】【第6章】继承与面向对象设计(4)

    条款39:明智而审慎的使用private继承
    1、如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。

    例子:

    #include<iostream>
    #include<string>
    using namespace std;
    
    class Person{
    protected:
    	string name;
    };
    class Student :private Person{
    private:
    	string schoolName;
    };
    void eat(const Person& p){
    	cout << "eat" << endl;
    }
    
    int main(){
    	Person p;
    	Student s;
    	eat(p);
    	//eat(s); //错误
    
    	system("pause");
    	return 0;
    }
    

    2、由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protectedpublic属性。
    3private继承意味着implemented-in-terms-of(根据某物实现出)。复合的意义也是如此。如何在两者间取舍?尽可能使用复合,必要时才使用private继承。必要是指a、当protected成员或virtual函数牵扯进来的时候,b、当空间方面的利害关系足以踢翻private继承的支柱时。
    4、与复合比,private继承在空间上可以使空白基类最优化。
    例子:
    #include<iostream>
    using namespace std;
    
    class Empty{};
    class HoldAnInt{
    private:
    	int x;
    	Empty e;
    };
    
    int main(){
    	cout << sizeof(HoldAnInt) << endl; //VS 2013上输出8
    
    	system("pause");
    	return 0;
    }
    

    上述例子中sizeof(HoldAnInt)>sizeof(int),VS 2013中sizeof(Empty)的值是1,因为对于”大小为零之独立对象“,通常C++会安插一个char到空对象内。

    #include<iostream>
    using namespace std;
    
    class Empty{};
    class HoldAnInt:private Empty{
    private:
    	int x;
    };
    
    int main(){
    	cout << sizeof(HoldAnInt) << endl; //VS 2013上输出4
    
    	system("pause");
    	return 0;
    }
    

    上述例子中sizeof(HoldAnInt)==sizeof(int),这是所谓的EBO(empty base optimization;空白基类最优化),EBO一般只在单一继承下才可行。

    请记住:

    • Private继承意味着is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
    • 和复合(compoistion)不同,private继承可以造成empty base最优化。这对致力于"对象尺寸最小化"的程序库开发者而言,可能很重要。

    条款40:明智而审慎地使用多重继承

    1、多重继承往往会导致较多的歧异机会

    #include<iostream>
    using namespace std;
    
    class BorrowableItem{
    public:
    	void checkOut(){
    		cout << "BorrowableItem::checkout" << endl;
    	}
    };
    class ElectronicGadget{
    private:
    	bool checkOut() const{
    		cout << "ElectronicGadget::checkout" << endl;
    	}
    };
    class MP3Player:public BorrowableItem,public ElectronicGadget{};
    
    int main(){
    	MP3Player mp;
    	//mp.checkOut();//调用歧异
    	mp.BorrowableItem::checkOut();
    
    	system("pause");
    	return 0;
    }
    

    上述例子中,BorrowableItem类和ElectronicGadget类都有checkOut函数,MP3Player类同时继承BorrowableItem和ElectronicGadget的checkOut函数,所以在调用的时候无法确定调用哪个。虽然BorrowableItem内的checkOut是public,ElectronicGadget内的checkOut是private,但是在看到是否有个函数可取用之前,C++首先确认这个函数对此调用之言是最佳匹配。而上述例子中两个类的checkOut函数匹配程度相同,所以才会造成歧异。解决这个歧异的方法是明白指出要调用哪一个基类内的函数。

    2、“钻石型多重继承”也是多重继承会产生的一种情况。

    基类的成员会经每一条路径被复制,最终的继承类中会有多笔基类的成员。解决这个问题的方法是让那个基类成为虚基类,即让继承它的类采用虚继承。例子见http://blog.csdn.net/ruan875417/article/details/46408475这篇文章的三、重复多重继承(带成员变量、虚函数、虚函数覆盖)和五、钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)。

    使用虚继承的那些类所产生的对象往往比使用非虚继承的类产生的对象体积大,访问虚基类的成员变量时,也比访问非虚基类的成员变量速度慢。虚基类的成本还包括其他方面。虚基类初始化的规则比非虚基类的情况复杂且不直观。虚基类的初始化责任是由继承体系中的最低层(most derived)class负责,这暗示a、class若派生自虚基类而需要初始化,必须认知其虚基类——不论那些基类距离多远,b、当一个新的继承类加入继承体系中,它必须承担其虚基类的初始化责任。

    3、多重继承也有它的合理用途。它的一个通情达理的应用是将“public继承自某接口”和“private继承自某实现”结合在一起。

    class Iperson {
    public:
    	virtual ~IPerson();
    	virtual std::string name() const = 0;
    	virtual std::string birthDate() const = 0;
    };
    
    class DatabaseID { ... };    //稍后被使用
    
    class PersonInfo {           //这个class有若干有用函数,可用以实现IPerson接口
    	explicit PersonInfo(DatabaseID pid);
    	virtual ~PersonInfo();
    	virtual const char* theName() const;
    	virtual const char* theBirthDate() const;
    	virtual const char* valueDelimOpen() const;
    	virtual const char* valueDelimClose() const;
    };
    
    class CPerson : public Iperson, private PersonInfo {     //注意,多重继承
    public:
    	explicit Cperson(DatabaseID pid) : PersonInfo(pid) {}
    	virtual std::string name() const {        //实现必要的IPerson成员函数
    		return PersonInfo::theName();
    	}
    	virtual std::string birthDate() const {     //实现必要的IPerson成员函数
    		return PersonInfo::theBirthDate();
    	}
    private:
    	const char* valueDelimOpen() const {       //重新定义继承而来的virtual“界限函数”
    		return "";
    	}
    	const char* valueDelimClose() const {
    		return "";
    	}
    };

    请记住:

    • 多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
    • virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
    • 多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    持久层框架:MyBatis 3.2(2)
    持久层框架:MyBatis 3.2(1)
    循环结构(二)
    Android LayoutInflater详解
    为什么调用 FragmentPagerAdapter.notifyDataSetChanged() 并不能更新其 Fragment?
    Android Support v4、v7、v13的区别和应用场景
    Android的string-array数据源简单使用
    FragmentTabHost切换Fragment时避免重复加载UI
    Ubuntu 安装Chrome步骤
    慢慢来,让好习惯自然来
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785436.html
Copyright © 2011-2022 走看看