1.导读
万丈高楼平地起,勿在浮沙筑高台
我们的目标
在先前基础课程所培养的正规、大器的编程素养上,继续探讨更多技术。
泛型编程和面向对象编程虽然分为不同思维,但他们正是C++的技术主流,所以本课程也讨论template(模板)。
深入探索面向对象之后继承关系所形成的对象模型,包括隐藏在底层的this指针,vptr(虚指针),vtbl(虚表),virtual mechanism(虚机制),以及虚函数(virtual functions)造成的polymorphism(多态)效果。
2.conversion function,转换函数
注意:转换类型不一定是需要基本类型,只要是定义过或者出现过的类型就可以转换。class Fraction
{
public:
Fraction(int num,int den=1)
: m_numerator(num),m_denominator(den){ }
operator double() const {//将Fraction转换为double,转换没有参数(返回类型转换函数可以不写返回类型)
return (double) (m_numerator/m_denominator);//转换函数通常是const函数,该加就要加
}
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 ctor
one-argument:指的是只需要一个实参(另一个有默认值),当然也可以给两个。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 d = f + 4; //调用non-explicit ctor将4转为Fraction,然后调用operator+
二者共存
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 d = f + 4; //【Error】 ambiguous(编译器不知道走哪条路)
explicit
关键字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 d = f + 4; //【Error】conversion from 'double' to 'Fraction' request.
示例
标准库中的相关代码: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)); }
...
4.pointer-like classes
关于智能指针
template<class T>
class share_ptr
{
public:
T& operator*() const
{ return *px; }
T* operator->() const
{ return px; }
share_ptr(T* p): px(p) { }
private:
T* px;
long* pn;
...
};
struct Foo
{
...
void method(void) { }
};
share_ptr<Foo> sp(new);
Foo f(*sp);
sp->method();(相当于px->method();)
关于迭代器
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 operaor != (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; }
}
///
reference operator*() const { return (*node).data; }
pointer operator->() const { return &(operator*()); }
///
list<Foo>::iterator iter
...
*ite;//获得一个Foo object()
ite->method();
//意思是调用Foo::method();
//相当于(*ite).method();
//相当于(&(*ite))->method();
5.function-like classes,所谓仿函数
template<class T>
struct identity {
const T&;
operator() (const T& x) const { return x; }
};
template <class Pair>
struct selectlst {
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; }
};
template <class T1,class T2>
struct pair{
T1 first;
T2 second;
pair(): first();
pair(const T1& a,const T1& b):
first(a),second(b){ }
....
};
结构体继承类,标准库里面有很多仿函数。
6.namespace经验
把代码块包起来,访问命名冲突。namespace 命名
{
}
7.class template,类模板
template<typename T>
class complex
{
public:
complex (T r = 0,T i = 0)
: re(r),im(i) { }
complex& operator += (const complex&);
T readl() const { return re; }
T imag() const { return im; }
private:
T re,im;
friend complex& __doapl (complex*,const complex&);
};
{
complex<double> x1(2.5,1.5);
complex<int> c2(2,6);
...
}
8.Function Template,函数模板
编译器会对function template进行实数推导stone r1(2,3),r2(3,3),r3;
r3 = min(r1,r2);
//使用下面的函数模板进行比较
template <class T>
inline const T& min(const T& a,const T& b)
{
return b < a ? b : a;
}
//使用下面的运算符重载进行比较,调用stone::operator<
class stone
{
public:
stone(int w,int h,int we)
: _w(w),_h(h),_weight(we) { }
bool operaotr< (const stone& rhs) const
{ return _weight < rhs._weight; }
private:
int _w,_h,_weight;
}'
9.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) { }
template <class U1.class U2>
pair(const pair<U1, U2>& p)
: first(p.first), second(p.second){ }
};
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>());
把一个由鲫鱼和麻雀组成的pair,放进(拷贝到)一个由鱼类和鸟类组成的pair中,可以吗?(可以,子类继承了父类)
反之可以吗?(不可以,父类不能调用子类)
template <typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>
{
...
template<typename _Tp1>
explicit shared _ptr(_Tp1* __p)
: __shared_ptr<_Tp>(__p){ }//智能指针为了实现这个功能这样写的,具体不太理解(打个记号)
...
};
Base1 * ptr = nde Derived(); //up-cast(这种写法可以,被称为指针上移)
shared_ptr<Base1> sptr(new Derived1); //模拟up-cast
10.specialization,模板特化
tempalte <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.partical sepcialization,模板偏特化
个数上的偏
将参数某个类型定下来的情况称为模板个数上的偏特化,必须要从左到右边进行偏特化template<typename T,typename Alloc=......>
class vector
{
...
};
template<typename Alloc=......>
class vector<bool,Alloc>
{
...
}
范围上的偏
template <typename T>
class C
{
...
};
//范围上的偏,指明类型
template<typename T>
class C<T*>
{
...
};
//这样也可以
template <typename U>
class C<U*>
{
...
};
//调用
C<string> obj1;
C<string*> obj2;
12.template template parameter,模板模板参数
template<typename T,
template <typename T>
class Container
>
class XCls
{
private:
}
13.关于C++标准库
统统用一遍,统统测试一遍。
注意:在编译器中输出__cplusplus查看编译器支持的C++的版本。
14.三个主题
取出C++11中的三个主题进行描述。
variadit templates(since C++11),数量不定的模板参数
void print()//注意这个需要存在,当参数只有0个的时候会被调用
{
}
template<typename T,typename... Types>
void print(const T& firstArg,const Types&... args)
{
cout<<firstArg<<endl;
print(args...);
}
注意:此处代码中的...是函数参数。//递归调用
print(7.5,"hello",bitset<16>(377),42);
//输出
7.5
hello
0000000101111001
42
如果想知道后面的一包是多少,可以使用sizeof...(args)来判断。
auto(since C++11)
语法糖,自动推测变量类型,在声明auto的时候必须赋值使得可以让编译器推测出类型。list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(),c.end(),target);
//也可以使用下面的方法
list<string> c;
...
auto ite = find(c.begin(),c.end(),target);
//错误用法
list<string> c;
...
auto ite;(×,此处编译器无法推测)
ite = find(c.begin(),c.end(),target);
ranged-base for(since C++11)
语法糖,方便快捷。for(decl : coll){
statement
}
for(int i : {2,3,5,7,9,13,17,19} ){//注意C++可以直接写一个容器包含元素{2,3,5,7,9,13,17,19}
cout<< i << endl;
}
vector<double> vec;
...
for (auto elem : vec){
cout << elem << endl;
}
for (auro& elem : vec){
elem *= 3;
}
15.Reference
注意:
1.sizeof(r) == sizeof(x)
2.&x == &r;
object和其reference的大小相同,地址也相同(全都是假象)
Java里面所有变量都是referenceint x = 0;
int* p = &x;
int& r = x; //r代表x。现在r,x 都是0(声明reference必须赋初值,且不能更改;指针可以)
int x2 = 5;
r = x2; //r不能重新代表其他物品。现在r,x都是5
int& r2 = r; //现在r2是5(r2代表r:亦相当代表x)
示例
object和其reference的大小相同,地址也相同(全都是假象),虽然在reference与其被引用对象相同,但是本质是指针。typedef struct Stag { int a,b,c,d; } s;
int main(){
double x = 0;
double* p = &x;//p指向x,p的值是x的地址
double& r = x; //r代表x,现在r,x都是0
cout<<sizeof(x)<<endl; //8
cout<<sizeof(p)<<endl; //4
cout<<sizeof(r)<<endl; //8
cout<<p<<endl; //0065FDFC
cout<<*p<<endl; //0
cout<<x<<endl; //0
cout<<r<<endl; //0
cout<<&x<<endl; //0065FDFC
cout<<&r<<endl; //0065FDFC
S s;
S& re = s;
cout<<sizeof(s)<<endl; //16
cout<<sizeof(rs)<<endl; //16
cout<<&s<<endl; //0065FDE8
cout<<&rs<<endl; //0065FDE8
}
reference的常见用途
reference通常不用于声明变量,而用于参数类型(parameters type)和返回类型(return type)的描述。void func1(Cls* pobj) { pobj->xxx(); }
void func2(Cls obj) { obj.xxx(); }
void func3(Cls& obj) { obj.xxx(); }
... 被调用段写法相同,很好
Cls obj;
func1(&obj); ————接口不同,困难
func2(obj); ————调用端接口相同,很好
func3(obj); ————调用端接口相同,很好
调用相同导致的问题,以下被视为"same signature"(所以二者不能同时存在):double imag(const double& im) {...}
double imag(const double im) {...} // Ambiguity
Q:const是不是函数签名的一部分?
A:是,两个函数一模一样,一个有const一个无const可以并存。
16.复合&继承关系下的构造和析构
Inheritance(继承)关系下的构造和析构
base class的dtor必须是virtual,否则会出现undefined behavior
构造函数由内而外
析构函数由外而内
Composition(复合)关系下的构造和析构
构造函数由内而外
析构函数由外而内
Inheritance+Composition关系下的构造和析构
到底是先Base还是先Component呢,不同编译器之间有所不同,但是不影响逻辑;但是侯捷老师所使用的编译器顺序是(使用打印来观察):
构造函数由内而外
Derived的构造函数首先调用Base的default构造函数,然后调用Component的default的构造函数,然后才执行自己。
析构函数由外而内
Derived的析构函数首先执行自己,然后调用Component的析构函数,然后调用Base的析构函数。
17.关于vptr和vtbl(难点)
对象模型(Object Model):关于vptr和vtbl
class A{
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_datal,m_data2;
};
class B : public A{
public:
virtual void vfunc1();
void func2();
private:
int m_data3;
};
class C : public B{
public:
virtual void vfunc1();
void func2();
private:
int m_data1,m_data4;
}
函数继承是指继承权,而非继承大小。
C++看到一个函数调用时静态绑定还是动态绑定,动态绑定的三种情况:
通过指针进行调用;
这个指针向上转型(up-cast);
调用虚函数(虚机制);
//用C的语法实现
(*(p->vptr)[n])(p);
或
(*p->vptr[n])(p);
18.关于this
对象模型(Object Model):关于this
this->Serialize(); 编译成 ((*this->vptr)[n])(this);
19.关于Dynamic Binding
动态绑定3要素:
是指针类型
调用虚函数
向上转型
谈谈const
const member functions(常量成员函数)
当成员函数的const和non-const版本同时存在,const object只会(只能)调用const版本,non-const object只会(只能)调用non-const版本。
const object (data members不得变动)
non-const object(data members可变动)
const member functions(保证不更改deta members)
√
√
non-const member functions(不保证deta members)不变
×
√
const String str("hello world");
str.print();//常量对象调用非常量函数
如果当初设计string::print()时未指明const,那么上行便是经由const object调用non-const member function,会出错,此非吾人所料。
non-const member functions可调用const member functions,反之则不行。class template std::basic_string<...>
有如下两个member functions:
charT
operator[] (size_type pos) const
{
...//不必考虑COW(两个版本同时存在,智能const对象调用)
}
reference operator[] (size_type pos)
{
...//必须考虑COW
}
COW:Copy On Write
const函数类型属于重载。
20.关于New,Delete(operator new,operator delete)
new和delete虽然内部编译为对应步骤不能更改,但是可以重载对应的操作符。
重载::operator new,::operator delete,::operator new[],::operator delete[]
//小心这影响是全局的void* myAlloc(size_t size)
{ return malloc(size); }
void myFree(void* ptr)
{ return free(ptr); }
//它们不可以被声明在一个namespace内
inline void* operator new(size_t size)
{
cout<<"jjhu global new() \n";
return myAlloc(size);
}
inline void* operator new[](size_t size)
{
cout<<"jjhu global new[]() \n";
return myAlloc(size);
}
inline void* operator delete(void* ptr)
{
cout<<"jjhu global delete() \n";
return myFree(ptr);
}
inline void* operator delete[](void* ptr)
{
cout<<"jjhu global delete[]() \n";
return myFree(ptr);
}
重载member operator new/delete
重载new和deleteclass Foo{
public:
void* operator new(size_t);
void operator delete(void*,size_t);
//...
}
Foo* p = new Foo;
...
delete p;
//接管到内存new
try{
void* mem = operator new(sizeof(Foo));
p = static_cast<Foo*>(mem);
p->Foo::Foo();
}
/
//接管到内存delete
p->~Foo();
operator delete(p);
/
重载new[]和delete[]class Foo{
public:
void* operator new[](size_t);
void operator delete[](void*,size_t);
//...
}
Foo* p = new Foo;
...
delete p;
//接管到内存new
try{
void* mem = operator new(sizeof(Foo)*N + 4);
p = static_cast<Foo*>(mem);
p->Foo::Foo();//N次
}
/
//接管到内存delete
p->~Foo(); //N次
operator delete(p);
/
示例,接口
class Foo
{
public:
int _id;
long _data;
string _str;
public:
Foo():_id(0) {cout<<"default ctor.this="<<this<<"id="<<_id<<endl; }
Foo(int i):_id(i) {cout<<"default ctor.this="<<this<<"id="<<_id<<endl; }
//virtual
~Foo() { cout<<"dtor.this="<<this<<"id="<<_id<<endl; }
static void* operator new(size_t size);
static void operator delete(void* pdead,size_t size);
static void* operator new[](size_t size);
static void operator delete[](void* pdead,size_t size);
}
void* Foo::operator new(size_t size)
{
Foo* p = (Foo*)malloc(size);
cout<<....
return p;
}
void Foo::operator delete(void* pdead,size_t size)
{
cout<<....
free(pdead);
}
void* Foo::operator new[](size_t size)
{
Foo* p = (Foo*)malloc(size);
cout<<....
return p;
}
void Foo::operator delete[](void* pdead,size_t size)
{
cout<<....
free(pdead);
}
//调用
Foo* pf = new Foo;
delete pf; //使用上面定义的接口函数(若无member就调用globals)
//下面强制时候用globals
Foo* pf = ::new Foo; //void* ::operator new(size_t);
::delete pf; //void ::operator delete(void*);
23.重载new(),delete(),$示例
我们可以重载class member operator new(),写出多个版本,前提是每一版本的声明都必须有独特的参数列,其中第一参数必须是size_t,其余参数以new所指定的placement arguments为初值。出现于new (... )小括号内的便是所谓的placement arguments。Foo* pf = new (300,'c') Foo;
我们也可以重载class member operator delete(),写出多个版本。但它们绝不对被delete调用。只有当new所调用的ctor抛出exception,才会调用这些重载版operator delete()。它只可能这样被调用,主要用来抛出未能完全创建成功的object所占有的memory。class Foo{
public:
Foo() { cout<<"Foo::Foo"<<endl; }
Foo(int) { cout<<"Foo::Foo"<<endl; throw Bad(); }//故意抛出异常
//(1)这个就是一般的operator new()的重载
void* operator new(size_t size){
return malloc(size);
}
//(2)这个就是标准库已提供的placement new()的重载(的形式)
// (所以我也模拟standard placement new,就只是传回pointer)
void* operator new(size_t size,void* start){
return start;
}
//(3)这个才是崭新的placement new
void* operator new(size_t size,long extra){
return malloc(size+extrea);
}
//(4)这又是一个placement new
void* operator new(size_t size,long extra,char init){
return malloc(size+extra);
}
//(5)这又是一个placement new,但故意写错第一参数的type(那必须是size_t以符合正常的operator new)
void* operator new(long extra,char init){
//[Error]'operator new' takes type 'size_t'('unsigned int')
//as first parameter [-fpermissive]
return malloc(extra);
}
//以下是搭配上述placement new的各个所谓placement dalete。
//当ctor发出异常,这儿对应的operator(placement) delete就会被调用。
//其用途是释放对应之placement new分配所得的memory。
//(1)这个就是一般的operator delete()的重载
void operator delete(void*,size_t){
cout<<"operator delete(void*,size_t)"<<endl;
}
//(2)对应上页的(2)
void operator delete(void*,void*){
cout<<"oeprator delete(void*,void*)"<<endl;
}
//(3)对应上页的(3)
void operator delete(void*,long){
cout<<"operator delete(void*,long)"<<endl;
}
//(4)对应上页的(4)
void operator delete(void*,long,char){
cout<<"operator delete(void*,long,char)"<<endl;
}
private:
int m_i;
};
//即使operator delete(...)未能一一对应于operator new(...),也不会出现任何报错,你的意思是:放弃处理ctor发出的异常。
//测试代码
Foo* p1 = new Foo; //1
Foo* p2 = new (&start) Foo; //2
Foo* p3 = new (100)Foo; //3
Foo* p4 = new (100,'a') Foo; //4
Foo* p5 = new (100) Foo(1); //5
Foo* p6 = new (100,'a')Foo(1);
Foo* p7 = new (&start) Foo(1);
Foo* p8 = new Foo(1);
ctor抛出异常(奇怪,不同编译器处理不同)
24.Basic_String使用new(extra)扩充申请量 template<...>
class basic_string
{
private:
struct Rep{
...
void release() {if (--ref == 0) delete this; }
inline static void* operator new (sie_t,size_t);
inline static void operator delete (void*);
inline static Rep* create(size_t);//对应下面
...
};
...
};
template <class charT,class traits,class Allocator>
inline basic_string <charT,traits,Allocator>::Rep*
basic_string <charT,traits,Allocator>::Rep::create(size_t extra)
{
extra = frob_size(extra + 1);
Rep *p = new (extra) Rep;//对应下面
...
return p;
}
template<class charT,class traits,calss Allocator>
inline void * basic_string <charT,traits,Allocator>::Rep::
operator new(size_t s,size_t extra)
{
return Allocator::allocate(s + extra * sizeof(charT));
}
template<class charT,class traits,class Allocator>
inline void basic_string<charT,traits,Allocator>
operator delete(void * ptr)
...
后记
更新完毕!