C++程序设计兼谈对象模型(侯捷)
这是C++面向对象程序设计的续集笔记,仅供个人学习使用。如有侵权,请联系删除。
主要内容:涉及到模板中的类模板、函数模板、成员模板以及模板模板参数,后面包含对象模型中虚函数调用(动态绑定)的具体原理。
参考链接:
Youtube: C++面向对象高级开发(下)
Github:源码和PPT
文章目录
- C++程序设计兼谈对象模型(侯捷)
- 2 conversion function 转换函数
- 3 non-explicit-one-argument constructor
- 4 pointer-like classes
- 5 function-like classes
- 6 namespace经验谈
- 7 class template 类模板
- 8 function template 函数模板
- 9 member template 成员模板
- 10 specialization 模板特化
- 11 模板偏特化
- 12 模板模板参数
- 13 关于C++标准库
- 14 三个主题 variadic templates等
- 15 reference
- 16 复合&继承关系下的构造和析构
- 17 (对象模型)关于vptr和vtbl,虚指针和虚表
- 18 (对象模型)关于this
- 19 (对象模型)关于Dynamic Binding
- 后记
2 conversion function 转换函数
conversion function 转换函数
把这种东西转变为别的类型:把Fraction转变为double
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
operator double() const{ // 转换函数,没有return type返回类型, 没有参数
return (double) (m_numerator / m_denominator);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
}
// 使用
Fraction f(3, 5);
double d = 4 + f; // 调用operator double() 将f转换为0.6
3 non-explicit-one-argument constructor
one argument: 只要1个实参就够了, 注意和parameter的区别,下面的Fraction构造函数有两个parameter,但是只有一个argument。
non explicit one argument constructor的作用:可以把别的东西转变为这种类型
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction& f) {
return Fraction(......);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
}
// 使用
Fraction f(3, 5);
double d2 = f + 4; // 调用non-explicit constructor 将4转为 Fraction(4, 1), 然后调用operator+
如果 double转换和重载+操作符并存,编译器就会产生歧义,会报错
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
operator double() const { // 转换函数
return (double) (m_numerator / m_denominator);
}
Fraction operator+(const Fraction& f) {
return Fraction(......);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
}
// 使用
Fraction f(3, 5);
Fraction d2 = f + 4; // 【Error】ambiguous
explicit-one-argument constructor
explicit关键字的作用是防止类构造函数的隐式自动转换.
class Fraction
{
public:
explicit Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
operator double() const { // 转换函数
return (double) (m_numerator / m_denominator);
}
Fraction operator+(const Fraction& f) {
return Fraction(......);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
}
// 使用
Fraction f(3, 5);
Fraction d2 = f + 4; // 【Error】conversion from "double" to "Fraction" requested.
转换函数在标准库中的例子
template<class Alloc>
class vector<bool, Alloc>
{
public:
typedef __bit_reference reference;
protected:
reference operator[](size_type n) {
return *(begin() + difference_type(n));
}
...
}
struct __bit_reference {
unsigned int* p;
unsigned int mask;
...
public:
operator bool() const {return !(!(*p & mask));}
...
}
如下图所示:一个vector里面保存的都是bool值,然后返回的是reference类型,这里就要有bool的转换函数。另外下图中还涉及到一种设计模式:proxy,具体关于proxy的知识不展开。
4 pointer-like classes
pointer like classes 关于智能指针
为什么要把一个类设计出来像一个指针呢?比指针做的事情更多一点
template<class T>
class shared_ptr
{
public:
T& operator*() const //*号操作符重载
{ return *px;} // 传指针指向的内容
T* operator->() const // -> 操作符重载
{ return px;}
shared_ptr(T* p): px(p) {}
private:
T* px;
long* pn;
};
struct Foo
{
...
void method(void) {......}
}
// 使用
shared_ptr<Foo> sp(new Foo); // 传一个指针进来
Foo f(*sp); // 使用*这个操作符
sp->method(); // 使用->这个操作符
pointer like classes,关于迭代器
迭代器也像指针,指向一个元素
template<class T>
struct __list_node {
void* prev;
void* next;
T data;
};
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
link_type node;
bool operator==(const self& x) const {return node == x.node;}
bool operator!=(const self& x) const {return node != x.node;}
reference operator*() const {return (*node).data;}
pointer operator->() const {return &(operator*());}
self& operator++() { node = (link_type)((*node).next); return *this;}
self operator++(int) { self tmp = *this; ++*this; return tmp;}
self& operator--() { node = (link_type)((*node).prev); return *this;}
self operator--(int) { self tmp = *this; --*this; return tmp;}
}
T&
operator*() const {return (*node).data;} // 对迭代器解参考,就是拿链表节点中的data
T*
operator->() const {return &(operator*());} // 获得上面operator*操作的地址
5 function-like classes
function like classes,所谓仿函数
重载()符号的用意,就是让这个类创建出来的对象是函数对象。
template<class T1, class T2>
struct pair {
T1 first;
T2 second;
pair(): first(T1()), second(T2()) {}
pair(const T1& a, const T2& b)
: first(a), second(b) {}
};
template<class T>
struct identity {
const T&
operator()(const T& x) const {return x;}
};
template <class Pair>
struct select1st {
const typename Pair::first_type&
operator()(const Pair& x) const
{ return x.first;}
};
template <class Pair>
struct select2nd {
const typename Pair::second_type&
operator()(const Pair& x) const
{ return x.second;}
};
标准库中的仿函数的奇特模样:继承自unary_function
template<class T>
struct identity: public unary_function<T, T> {
const T&
operator()(const T& x) const {return x;}
};
template <class Pair>
struct select1st: public unary_function<Pair, typename Pair::first_type> {
const typename Pair::first_type&
operator()(const Pair& x) const
{ return x.first;}
};
template <class Pair>
struct select2nd: public unary_function<Pair, typename Pair::second_type> {
const typename Pair::second_type&
operator()(const Pair& x) const
{ return x.second;}
};
还有继承自binary_function的仿函数
标准库中,仿函数所使用的奇特的base classes
6 namespace经验谈
命名空间起到隔离的作用,不同的命名空间里面可以有相同的函数名、变量名等,但是它们属于不同的范围。
7 class template 类模板
class template,类模板
在设计一个类的时候,允许某个变量或者参数的类型由使用者任意指定,那么就可以把这个类称为模板类,或者叫类模板。
8 function template 函数模板
function template,函数模板
在使用时,不用指明参数的type,编译器会进行实参推导
9 member template 成员模板
member template 成员模板
在标准库中的构造函数中会出现大量的member template,为的是让构造函数更有弹性一些,比如用派生类来初始化基类。
template<class T1, class T2>
struct pair{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2()) {}
pair(const T1& a, const T2& b): first(a), second(b) {}
#ifdef __STL_MEMBER_TEMPLATES
template<class U1, class U2> // 成员模板
pair(const pair<U1, U2>& p): first(p.first), second(p.second) {}
#endif
};
下面以鲫鱼继承鱼类,麻雀继承鸟类来展示成员函数。
把一个鲫鱼和麻雀构成的pair,放进一个鱼类和鸟类构成的pair,这个是可以的。
class Base1 {};
class Derived1: public Base1 {};
class Base2 {};
class Derived2: public Base2 {};
pair<Derived1, Derived2> p;
pair<Base1, Base2> p2(p);
pair<Base1, Base2> p2(pair<Derived1, Derived2>()); //把一个派生类1和派生类2构成的pair,放进一个基类1和基类2构成的pair,反过来不可以
template<typename _Tp>
class shared_ptr: public __shared_ptr<_Tp>
{
template<typename _Tp1>
explicit shared_ptr(_Tp1* __p)
: __shared_ptr<_Tp>(__P) {}
};
Base* ptr = new Derived1; // up-cast 向上转型是可以的
shared_ptr<Base1> sptr(new Derived1);
智能指针模拟向上转型:可以用派生类来初始化基类
10 specialization 模板特化
specialization 模板特化:限定模板实现的具体类型,比如下面指定hash的类型为char,int和long。
template<class Key>
struct hash {};
// 特化
template<>
struct hash<char> {
size_t operator()(char x) const {return x;}
};
template<>
struct hash<int> {
size_t operator()(int x) const {return x;}
};
template<>
struct hash<long> {
size_t operator()(long x) const {return x;}
};
使用过程如下:
cout << hash<long>()(1000);
11 模板偏特化
模板偏特化——个数的偏
模板中可以指定某些参数为特定类型。
模板偏特化——范围的偏
从指向任意类型T,变成指向任意类型的指针*T,范围变小了。
12 模板模板参数
template template parameter,模板模板参数
可以让模板参数它本身是个类模板。下图中第二个参数为模板模板参数Container,它接收第一个模板参数来实例化自己,比如接收下面T类型。
template<typename T, template<typename T> class Conatainer>
class XCls
{
private:
Container<T> c;
public:
...
}
13 关于C++标准库
Containers、Iterators、Algorithms、Functors的等使用,有另外一门课进行剖析。
14 三个主题 variadic templates等
variadic templates 可变参数模板
使用3个点…来表示
template<typename T, typename... Types>
void print()
{
}
void print(const T& firstArg, const Types&... args)
{
cout << firstArg << endl;
print(args...);
}
auto 用法:自动推导变量类型
之前的用法
list<string> c;
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
C++11的用法
list<string> c;
auto ite = find(c.begin(), c.end(), target);
ranged-base for
能传引用就传引用,速度快。
vector<double> vec;
for(auto elem: vec) {
cout << elem << endl;
}
for (auto& elem: vec) { // 改变原来的数据,要传引用&
elem *= 3;
}
15 reference
int x = 0;
pointer to interger: int *p = &x;
reference to interger: int& r = x;
r不是指针,r代表x, x的地址在哪,r的地址就在哪。 但是底层实现的时候使用指针实现的。
如果 int x2 = 5; r = x2;
由于r已经代表了x,它不能重新代表其他的东西,经过r = x2;
之后,r和x都变成了5.
reference引用的常见用途
16 复合&继承关系下的构造和析构
单独的继承关系、符合关系的继承与析构请参加我的笔记C++面向对象高级编程(侯捷)笔记2中关于11 组合与继承的部分。
这里记录继承+组合关系下的构造和析构
构造由内而外
Derived的构造函数首先调用Base的默认构造函数,然后调用Component的默认构造函数,然后执行自己。
析构由外而内
Derived的析构函数首先执行自己,然后调用Component的析构函数,然后调用Base的析构函数。
17 (对象模型)关于vptr和vtbl,虚指针和虚表
下面开始谈Object Model的内容。
虚指针vptr和虚表vtbl
virtual function虚函数有两个步骤来支持:
- 每一个class产生出一堆指向virtual functions的指针,放在virtual table(vtbl)中。
- 每一个class object被添加了一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设置和重置都由每个class的构造函数、析构函数和拷贝赋值运算符自动完成。
参考:深度探索C++对象模型
继承中,子类的对象里面有父类的成分
调用虚函数的过程,通过vptr得到vtbl,然后查找表中的第n个函数
(*(p->vptr[n]))(p); 或
(*p->vptr[n])(p);
18 (对象模型)关于this
对象模型object model:关于this
动态绑定的三个条件:
- 通过指针调用
- 指针有向上转型的动作
- 调用虚函数
编译器对动态绑定动作:
把
this->Serialize();
编译成下面这种形式:虚指针指向虚表,调用特定的虚函数
(*(this->vptr)[n])(this);
19 (对象模型)关于Dynamic Binding
对象模型:关于Dynamic Binding
静态绑定:直接调用汇编的call指令,跳转到函数地址。
B b;
A a = (A)b;
a.vfunc1(); // 静态绑定,直接call vfunc的地址
动态绑定:虚指针查虚表得到虚函数的地址,进行调用。
A* pa = new B; // 向上转型
pa->vfunc1();
后记
从2024年1月1日开始,截至2024年1月4日,共花费4天,学习完C++面向对象高级编程(上)和C++面向对象高级编程(下),其中后者的标题为C++程序设计兼谈对象模型。