1 概述
通过继承机制,可以利用已有的对象类型来定义新的对象类型。所定义的新的对象类型不仅仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
2 对象访问作用域
作用域:
- public(公共作用域) 对象自身、派生类内部,对象外部都可以访问。
- protected(保护作用域) 对象自身、派生类内部可以访问。
- private(私有作用域) 只有对象自身可以访问。
说明:
- 友元函数和友元类不受上述作用域限制。
- protected(保护作用域)就是为了派生使用的。
3 继承
继承分类:
- public继承 派生类中父类public成员还是public成员。
- protected继承 派生类中父类public成员变成protected成员。
- private继承 派生类中父类public成员变成private成员。
- 多重继承 派生类可以从多个父类派生。
- 虚继承
3.5 虚继承
虚继承是面向对象编程中的一个重要概念,特别是在C++语言中用于解决多重继承带来的问题。当两个或更多的类继承自同一个基类时,如果没有使用虚继承,每个派生类都会包含基类的一个副本,这可能导致数据冗余和内存浪费。虚继承通过确保基类在派生类中只有一个共享的副本,来解决这个问题。
虚继承的工作原理:
虚基类指针:在C++中,使用虚继承时,编译器会在派生类中创建一个虚基类指针(vptr),以及一个虚基表(vtable)。虚基表存储了到虚基类成员的偏移量。访问虚基类成员:当需要通过派生类访问虚基类的成员时,派生类通过其虚基类指针查找虚基表,从而找到成员的地址并访问之。
虚继承的优点:
- 节省内存:通过共享虚基类的单一实例,避免了数据冗余。
- 避免二义性:在多重继承中,使用虚继承可以减少成员访问的二义性问题。
3.5.1 例子
在C++中,使用virtual关键字来声明一个继承关系为虚继承,例如:
#include <iostream>
#include <stdint.h>struct IoBase
{IoBase(uint8_t *data, uint32_t maxSize): data_(data), maxSize_(maxSize), w_pos_(0), r_pos_(0){std::cout << "IoBase is called" << std::endl;}void print(){if(isEmpty()){std::cout << "Stream is empty!" << std::endl;return;}std::cout << "Stream:\n " << std::hex;for(uint32_t i = r_pos_; i < w_pos_; i++)std::cout << static_cast<int>(data_[i]) << " ";std::cout << std::dec << std::endl;}uint32_t writeSpace() const { return maxSize_ - w_pos_; }uint32_t size() const { return w_pos_ - r_pos_; }bool isEmpty() const { return w_pos_ == r_pos_; }
protected:uint8_t* data_;uint32_t w_pos_;uint32_t r_pos_;
private:uint32_t maxSize_;
};struct Ostream : virtual IoBase
{Ostream(uint8_t *data, uint32_t maxSize): IoBase(data, maxSize){std::cout << "Ostream is called" << std::endl;}Ostream& operator << (uint32_t value){if(writeSpace() >= sizeof(value)){data_[w_pos_++] = (uint8_t)(value & 0xFF);data_[w_pos_++] = (uint8_t)((value >> 8) & 0xFF); data_[w_pos_++] = (uint8_t)((value >> 16) & 0xFF); data_[w_pos_++] = (uint8_t)((value >> 24) & 0xFF); }return *this;}uint32_t writePos() const { return w_pos_; }
};struct Istream : virtual IoBase
{Istream(uint8_t *data, uint32_t maxSize): IoBase(data, maxSize){std::cout << "Istream is called" << std::endl;}void setSize(uint32_t size) { w_pos_ = r_pos_ + size; }Istream& operator >> (uint32_t &value){if(size() >= sizeof(value)){value = (data_[r_pos_ + 3] << 24) | data_[r_pos_ + 2] << 16 | data_[r_pos_ + 1] << 8 | data_[r_pos_];r_pos_ += sizeof(value);}return *this;}
};struct IOStream : Ostream, Istream
{IOStream(uint8_t *data, uint32_t maxSize): IoBase(data, maxSize), Ostream(data, maxSize), Istream(data, maxSize){}
};int main(int argc, char *argv[])
{uint8_t data[24];uint32_t a = 0x12345678;uint32_t b;IOStream io(data, sizeof(data));std::cout << "a=0x" << std::hex << a << std::endl;io << a;io.print();io >> b;std::cout << "b=0x" << std::hex << b << std::endl;io.print();return 0;
}
说明:
- IOStream需要显示调用IoBase构造函数,如下所示:
struct IOStream : Ostream, Istream
{IOStream(uint8_t *data, uint32_t maxSize): IoBase(data, maxSize), Ostream(data, maxSize), Istream(data, maxSize){}
};
3.5.2 运行结果
IoBase is called
Ostream is called
Istream is called
a=0x12345678
Stream:78 56 34 12
b=0x12345678
Stream is empty!
3.5.3 分析
从运行结果看:
- 父类IoBase构造函数指调用了一次。
- IOStream继承了Ostream和Istream功能,并且共享一份IoBase的数据。
3.5.4 总结
- 虚继承主要用在多继承场景中,单继承或只有一层继承关系时不会发挥作用。
- 过度使用虚继承可能会增加额外的开销,因为需要维护虚基类的地址偏移量和查找虚基类成员的位置。
我们可以看到虚继承不仅是一个技术手段,也是C++语言中解决多重继承问题的一个有效方法。