C++经验(十二)-- 复合、public继承、private继承

复合是类型之间的一种关系,当一种类型的对象包含了其他种类型的对象的时候,我们就可以说这是复合关系,复合关系也是我们在画UML类图的时候经常碰到的一种类与类之间的关系。

class Address{...};
class PhoneNumber{...};

class Person
{
public:
    ...
private:
    std::string name{};
    Address address{};
    PhoneNumber phone{};
}

上面的这个例子中类 Person 对象由 std::string , AddressPhoneNumber 构成。

我们都知道,public继承其实构建的是一种 is-a 的关系,就是如果派生对象是一种基类对象,那么对派生对象为真的事情对基类对象也一定为真。而复合构建的是一种 has-a 的关系,也就是说,某个对象内含了其他的某个对象。

复合也可以构建一种 is implemented in terms of 的关系,就是根据什么实现出什么。

上面的这个例子我们构建的就是一种 has-a 的关系,因为,我们可以说,人有一个地址,但不能说,人是一个地址。

比较麻烦的是很难区分 has-a 和 is implemented in terms of 这两种的关系

其实复合这种设计在我们平时的工作中会经常遇到。我们也经常根据一些比较方便的 template 来实现自己的数据结构,比如,在一个空间比时间重要的程序里面,我们可能会这样实现 set template

为什么不直接用 std::set 呢?因为模版库的set 在实现的时候为了在一些操作上保证对数时间的效率,每个元素都会有三个指针的额外开销。

template<typename T>
class Set : public std::list<T>
{
public:
    ...
}

这样对吗?上面的这个关系是 is-a 的关系,但是对Set来说,对他为真的某些事情对 list 并不一定为真,比如 Set不允许重复元素,但是 list 可以。

template<typename T>
class Set
{
public:
    bool member(const T& item) const;
    void insert(const T& item);
    void remove(const T& item);
    std::size_t size() const;
    ...
private:

    std::list<T> m_data;
}

template<typename T>
bool member(const T& item) const
{
    return std::find(m_data.begin(), m_data.end(), item) != m_data.end();
}

template<typename T>
bool insert(const T& item)
{
    if(!member(item)){ m_data.push_back(item); }
}

template<typename T>
bool remove(const T& item)
{
    auto it = std::find(m_data.begin(), m_data.end(), item);
    if(it != m_data.end())
    {
        m_data.erase(it);
    }
}

template<typename T>
std::size_t Set<T>::size() const
{
    return m_data.size()
}

我们都知道,public 继承是塑造了一种 is-a 的关系,那么如果我们手贱,不小心将public继承写成了private继承会怎么样呢?

#include<stdio.h>
#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	A(string a) : m_data(a){}
	~A(){}

	virtual void display() const
	{
		cout << "this is A function..." << m_data << endl;	
	}
	
private:

	string m_data{ "" };	
};


class B : private A
{
public:
	B(string a) : A(a), m_data(a){}
	~B(){}
	
	virtual void display() const
	{
		cout << "this is B function..." << m_data << endl;	
	}
	
private:

	string m_data{ "" };	
};

void displayByValue(const B& w)
{
	w.display();
}

void displayByReference(const A& w)
{
	w.display();
}

int main(int* argc, char* argv[])
{
	
	B b("this is a test...");

	displayByValue(b);
	displayByReference(b);
	
	return 0;	
}

上面的这个例子是我从前面的某篇文章中抄过来改了改,我们可以看到,第一个 displayByValue 能正常调用,但是 displayByReference 编译器会报错。 'A' is an inaccessible base of 'B'

如果继承关系是private那么编译器是不会自动的将一个派生类转换为基类的。同样的,派生类通过private继承继承到的成员的属性都将会被改为private。而无论这些成员在基类中是public 还是 protected。

private 继承意味着 is implemented in terms of 的关系。继承的是一种实现而不是接口。我们前面刚说过复合也是 is implemented in terms of 的关系,这就会让我们在实现的时候面对一个选择。如何取舍?尽可能使用复合,必要时再选择private继承。

通常级别比复合低,但当派生类需要访问基类的protected成员,或者需要重新定义继承而来的虚函数时,私有继承是比较好的选择。

private继承的访问控制

  • 基类的public和protected成员:都以private身份出现在派生类中;
  • 基类的private成员:不可直接访问。

private访问权限

  • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
  • 通过派生类的对象:不能直接访问从基类继承的任何成员

看下面这个例子。

class Empty{};
class Test
{
private:
	int a;
	Empty e;
};

int main(int *argc, char* argv[])
{
	Test t;
	std::cout << sizeof(t);
}

按照我们正常的理解,因为Empty是个空类,所以,应该是不会有任何内存控件的占用,但实际上,输出结果会大于 sizeof(int) 的大小,大多数编译器获取 sizeof(Empty)的大小应该是1。又由于对齐的原因,测试结果应该是 sizeof(int) 的 2 倍。

那如果按照下面的继承呢?

class Test : private Empty
{
private:
	int a;
};

这样的测试结果是4,也就是 sizeof(int) 的大小。这也就是空基类最优化。

所以, private继承通常意味着 is implemented in terms of 的一种关系,慎用。private继承能够实现空基类的最优化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值