【侯捷面向对象高级开发】第三部分:C++新特性

第三部分:C++新特性

2.1 导读

勿在浮沙筑高台

更多细节与深入

  • 泛型编程(Generic Programming)和面向对象编程(Object-Oriented Programming)虽然分属不同思维,但它们正是C++的技术主线,所以本课程也讨论模板模板(template)。
  • 深入探索面向对象继承关系(Inheritance)所形成的对象模型(Object Model),包含影藏于底层的this指针,vptr虚指针,vtbl虚表,virtual mechanism虚机制以及虚函数(virtual functions)造成的多态(polymorphism)效果。
  • 推荐书籍:

C++ Primer(第五版)

The C++ Programming Language(Fourth Edition)

Effective C++改善程序设计与设计思维的55个有效做法

The C++ Standard Library

STL源码剖析


2.2 conversion function, 转换函数

(1)使用场景:我自定义一个类,表示分数,但是我希望它做加减乘除的时候,自动转为double。

class Fraction { public: Fraction(int num, int den=1) : m_numerator(num),m_denominator(den) { } operator double() const //通常都会加const 不改变数据 { return (double)(m_numerator / m_denominator); } operator string() const { } private: int m_numerator; //分子 int m_denominator;//分母 }; //转换函数的应用:将Fraction型转换为double Fraction f(3,5); //构造函数 double d = 4 + f; //调用operator double()将f转为0.6

(2) non-explicit one argument constructor 非explicit单实参构造函数

class Fraction 
{ 
public: Fraction(int num, int den=1) //non-explicit-one-argument ctor 
                : m_numerator(num),m_denominator(den) { } 
    Fraction operator+(const Fraction& f)
    { 
        return Fraction(...); 
    } 
private: int m_numerator; //分子 
int m_denominator;//分母 
}; 
//转换函数的应用:调用non-explicit ctor将4转换为Fraction(4,1) 
// 然后调用operator+ Fraction f(3,5); 
Fraction d2 = f + 4;//调用non-explicit ctor将4转为Fraction(4,1), 将数据转为Fraction 
                    //然后调用operator+

设计模式里面的【代理】,就用到了转换函数。

class Fraction 
{ 
public: //explict只有用在一个参数的构造函数 
explict Fraction(int num, int den=1) : m_numerator(num),m_denominator(den) { }
//explicit-one-argument ctor ,explict告诉编译器不要将3变为3/1  
    Fraction operator+(const Fraction& f)
    { 
        return Fraction(...); 
    } 
private: int m_numerator; 
    //分子 int m_denominator;
    //分母 
};

conversion function 转换函数 模板中的应用举例

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)); 
    } //转换函数 
};

2.3 pointer-like classes, 关于智能指针

(1)关于智能指针

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); //调用智能指针的 operator*()重载函数 sp->method(); 
                //调用智能指针的 operator->()重载函数 
                //px->method(); 
                //->得到的东西要继续->作用下去(->的特殊之处)

像指针的类,用起来跟指针一样,但是有更多的机制(通过重载实现)。

链表的node也是一种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_node<T>* link_type; 
    link_type node; reference operator*() const 
    { 
        return (*node).data; 
    } //迭代器用*,就是要拿data 
    pointer operator->() const 
    { 
        return &(operator*()); 
    } 
}; 
//用法 list<Foo>::iterator ite; ... *ite; 
//获得一个Foo object ite->method(); 
//意思是调用Foo::method() 
//相当于(*ite).method(); 
//相当于(&(*ite))->method();

2.4 function-like classes, 所谓仿函数

(1)让一个类像函数被调用,只要实现()操作符重载即可。

小小的类,用作base unit。

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; } 
}; 
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) {} 
    ... 
}; 
select1st<Pair>()(); //第一个()创建临时对象

(2)标准库中的仿函数的奇特模样

template <class T> 
struct identity : unary_function<T,T> 
{ 
    const T& operator()(const T& x) const { return x; } 
}; 

template <class Pair> 
struct select1st : 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 : unary_function<Pair,typename Pair::second_type> 
{ 
    const typename Pair::second_type& operator()(const Pair& x) const 
    { return x.second; } 
}; 

template <class T> struct plus : binary_function<T,T,T> 
{ 
    T operator()(const T& x, const T& y) const 
    { return x+y; } 
}; 

template <class T> struct minus : binary_function<T,T,T> 
{ 
    T opetator()(const T& x, const T& y) cosnt 
    { return x-y; } 
}; 

template <class T> struct equal_to : binary_function<T,T,bool> 
{ 
    bool operator()(const T& x, const T& y) 
    { return x==y; } 
}; 

template <class T> struct less : binary_function<T,T,bool> 
{ 
    bool operator()(const T& x, const T& y) const 
    { return x<y;} 
};

(3)标准库中,仿函数所使用的奇特的base classes

template <class Arg, class Result> 
struct unary_function 
{ //大小就是把数据加起来 理论=0 实际=1 
    typedef Arg argument_type; 
    typedef Result result_type; 
}; 

template <class Arg1, class Arg2, class Result> 
struct binary_function { //大小就是把数据加起来 理论=0 实际=1 
    typedef Arg1 first_argument_type; 
    typedef Arg2 second_argument_type; 
    typedef Result result_type; 
}; 
less<int>::result_type->bool

2.5 namespace经验谈

using namspace std; 
//-------------------------------------------- 
#include <iostream> 
#include <memory> //shared_ptr 
namespace jj01 //命名空间jj01 
{ 
    void test_member_template() { ... } 
} //namespace jj01 
//-------------------------------------------- 
#include <iostream> 
#include <list> 
namespace jj02 //命名空间jj02 
{ 
    template<typename T> using Lst = list<T,alloctor<T>>; 
    void test_template_template_param() { ... } 
}//namespace jj02 
//-------------------------------------------- 
int main(int argc, char** argv) 
{ 
    jj01::test_member_template(); //通过命名空间调用 
    jj02::test_template_template_param(); 
    
    return 0;
}

2.6 class template 类模板

template<typename T> 
class complex 
{ 
public: 
    complex (T r=0,T i=0) : re(r), im(i) { } 
    complex& operator += (const complex& ); 
    T real() const { return re; } 
    T imag() const { return im; } 
private: T re, im; 
    friend complex& __doapl(complex*, const complex&); }; 

int main() 
{ 
    complex<double> c1(2.5, 1.5); 
    complex<int> c2(2,6); 
    ... 
}

2.7 Funtion Template 函数模板

template <class T> 
inline const T& min(const T& a, const T& b) 
{ 
    return b < a ? b : a; 
} 
class stone 
{ 
public: 
    stone (int w, int h, int we) : _w(w), _h(h), _weight(we) 
    { } 
    bool operator< (const stone& rhs) const 
    { 
        return _weight < rhs._weight;
    } 
private: int _w, _h, weight; 
}; 
//应用 
{ 
stone r1(2,3), r2(3,3), r3; r3 = min(r1, r2); //编译器会对function template进行实参推导(argument deduction) 
} 
template <class T> 
inline const T& min(const T& a, const T& b) 
{ 
    return b < a ? b : a; //实参推导的结果,T为stone,于是调用stone::operator< 
}

2.8 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){} 
};

STL中的成员模板的应用

template<typename _Tp> 
class shared_ptr:public __shared_ptr<_Tp> 
{ 
    ... 
    template<typename _Tp1> 
    explicit shared_ptr(_Tp1* __p):__shared_ptr<_Tp>(__p){} 
    ... 
}; 
Base1* ptr = new Derived1; //up-cast 
shared_ptr<Base1> sptr(new Derived1); //模拟up-cast


2.9 specialization,模板特化 (泛化就是模板)

//一般的泛化 
template <class Key> 
struct hash { }; 
//模板特化 
char int long template<> //hash<char>被绑定到char 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); //hash<long>()临时对象,(1000)调用operator()函数

2.10 partial specialization, 模板偏特化

1)个数的偏特化

template<typename T,typename Alloc=...> class vector { ... }; template<typename Alloc=...> class vector<bool,Alloc> //T绑定bool,减少了个数 { ... }

2)范围的偏特化

template <typename T> class C { ... }; template <typename T> class C<T*> //范围缩小为指针 { ... }; //这样写也可以 template <typename U> class C<U*> { ... }; C<string> obj1; C<string*> obj2;


2.11 template template parameter 模板模板参数(高级应用)

template<typename T, template <typename T> class Container > 
//在模板template<typename ...> = template<class ...> 

class XC1s 
{ 
private: 
    Container<T> c; 
public: 
... 
}; 
template<typename T> 
using Lst = List<T,allocator<T>>; 
XCls<string,Lst> mylst2; //Lst是个模板,为了可传入任何类型,外部再套一层模板

template<typename T, template <typename T> class SmartPtr > class XC1s { private: SmartPtr<T> sp; public: XC1s() : sp(new T) { } };

这不是template template parameter ???不是太懂,看书好好消化


2.12 关于C++标准库(使用它熟用它)

1)Sequence containers:

array

vector

deque

forward_list

list

2) Associative containers:

set

multiset

map

multimap

3) Container adaptors:

stack

queue

priority_queue

4) Algorithms

Sorting:

sort

stable_sort

partial_sort

partial_sort_copy

is_sorted

is_sorted_until

nth_element

Binary_search:

lower_bound

upper_bound

equal_range

binary_search

lower_bound

upper_bound

equal_range

binary_search

Merge:

merge

inplace_merge

includes

set_union

set_intersection

set_difference

set_symmetric_difference

了解你的编译器对C++2.0的支持度,

C++11 complier support shootout: Visual Studio, GCC, Clang, Intel (搜索主题)

DevC++上的ISO C++开关

确认支持C++11:macro __cplusplus

#include <iostream> int main() { std::cout<<__cplusplus; }


2.13 三个主题:

(1)variadic templates(since C++11) 数量不定的模板参数

print(7.5,"hello",bitset<16>(377),42); //函数1 void print() { } //模板函数2 template<typename T, typename... Types> //模板参数包 void print(const T& firstArg, cosnt Types&... args) //函数参数类型包 { //Inside valiadic templates,sizeof...(args)yields the number of arguments cout<<firstArg<<endl; print(args...); //用递归的方式 } //函数参数包 ...就是一个所谓的pack(包)

(2)auto (since C++11)

list<string> c; ... list<string>::iterator ite; ite = find(c.begin(),c.end(),target); //since C++11 auto编译器自动推导 list<string> c; ... auto ite = find(c.begin(),c.end(),target);

(3)ranged-base for(since C++11)

for( decl : coll ){ statement } for ( int i : {2,3,4,5,7,9,13,17,19} ){ cout<< i << endl; } vector<double> vec; ... for (auto elem : vec){ //pass by value cout<< elem << endl; } for (auto& elem : vec){ //pass by reference elem *= 3; }


2.14 Reference (引用)

object 和 其reference的大小相同,地址也相同(全都是假象)

int x = 0; 
int* p = &x; //p是指针变量,p is a pointer to x,p是一个变量,是int*(pointer to int)类型 
int& r = x; //r 代表 x.现在r,x都是0,r变量,r is a reference to x int x2 = 5; 
//注意:1、sizeof(r)==sizeof(x) 
// 2、&x == &r 
// 3、reference一定要定义时设初值,不可改变 r = x2; 
//r引用不能重新指向别的变量,r=x=5 int& r2 = r; //r2代表r也代表x,都是5

value本身、指针、引用(别名)

reference的常见用途:

reference通常不用于声明变量,而用于参数类型(parameters types)和 返回类型(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); 
//调用接口相同,很好 reference通常不用于声明变量,
而用于参数类型(parameters type)和返回值类型(return type)的描述 
一下被视为 "same signature"(所以二者不能相同) 
double imag(const double& im) {...} 
double imag(const double im) {...} 
----------------------signature相同 Ambiguity 函数后可加const区分 
Q:const 是不是函数签名的一部分? 
A:是的,其中一个加const后可以并存

2.15 复合&继承关系下的构造和析构

1)inheritance(继承)关系下的构造和析构

base class 的 dtor必须virtual, 否则会出现undefined behavior。

构造由内而外:

Derived的构造函数必须调用Base的default构造函数,然后才执行自己。

Derived::Derived(...) : Base() {...};

析构由外而内:

Derived的析构函数首先执行自己,然后才调用Base的析构函数。

Derived::~Derived(...){ ...~Base(); };

2) Composition(复合)关系下的构造和析构

构造由内而外:

Container的构造函数首先调用Componet的default构造函数,然后才执行自己。

Container::Container(...) : Component() {...};

析构由外而内:

Container的析构函数首先执行自己,然后才调用Component的析构函数。

Container::~Container(...) { ... Component() };

3)Inheritance + Compositon关系下的构造和析构

构造由内而外:

Derived的构造函数首先调用Base的default构造函数,然后调用Component的default构造函数,

然后才调用自己。

Derived::Derived(...) : Base(), Component() {...};

析构由外而内:

Derived的析构函数首先执行自己,然后调用Component的析构函数,

然后调用Base的析构函数。

Derived::~Derived(...){...~Component(),~Base() };


2.16 关于vptr和vtbl ,虚指针,虚表

只要类中有虚函数,就有虚指针vptr,它指向vtbl,表里面放的都是函数指针,指向内存里面的虚函数。

这幅图画得非常非常清晰。

父类有虚函数,子类也有虚函数。

动态绑定:

1)指针调用

2)向上转型

3)虚函数

这个也就是实现多态的底层原理。


2.17 关于this

Template Method 模板方法

this

//Application 
framework CDocument::OnFileOpen() 
{ 
... //常规动作 
        Serialize(); 
    ... 
} 
virtual Serialise(); //基类中关键的这部分不知道怎么做,需要继承后才知道定义为虚函数 
//Application 
class CMyDoc:public CDocument 
{ 
    virtual Serialize(){...} 
    // 
}; 
int main() 
{ 
    CMyDoc myDoc; 
    ... 
    myDoc.OnFileOpen(); // 
}

2.18 关于Dynamic Binding

静态绑定:直接访问内存里编译好的函数内存空间。

动态绑定:调用三个条件:

  1. 必须通过指针调用。(this指针调用即可满足)
  2. 必须是up-cast(子类向父类转型)(调用父类的函数即可满足)
  3. 必须调用的是虚函数 (父类需要动态绑定的函数定义为虚函数)

底层勘探

假如A是B的父类,B是C的父类。

//静态绑定 B b; A a= (A)b; //向上转型 a.vfunc1(); //调用父类的虚函数,这是静态绑定,汇编形式:call xxx //动态绑定 A* pa = new B; //向上转型 pa->vfunc1(); //调用父类的虚函数,动态绑定

2.19 谈谈const

这里C++ primer这本书说得贼全,但是很复杂。侯老师说得比较精炼。

当成员函数的const和non-const版本同时存在,

const object 智能调用const版本。

non-const object只能调用non-const版本。

const object

(data member不得改动)

non-const object

(data members可改动)

const member functions

(保证不改变data members)

允许

允许

non-const member functions

(不保证 dat members不变)

不允许

允许

即常量对象无法调用非常量成员函数。

const member functions(常量成员函数),const成员函数保证不更改data members。

COW: Copy and Write

const member function不必考虑COW(访问同一个内存空间的object)

但是non-const member function是必须考虑COW(申请一个新的内存空间,copy过去)。

当成员函数的const和non-const同时存在的时候,

C++特性:const object智能调用const版本,non-const object只能调用non-const版本。

//const是属于签名的一部分 charT operator[](size_type pos) const { ...//不必考虑COW(Copy On write) 常量字符串不可改 } reference operator[](size_type pos) { ...//必须考虑COW 非常量字符串调用 }


2.20 关于New,Delete

new: 先分配memory, 再调用ctor

String* ps = new String("Hello"); //编译器转化为 void *mem = operator new ("Hello"); //内部调用malloc(n) ps = static_cast<String*>(mem); ps->String::String("Hello"); //调用构造函数

delete: 先调用dtor,再释放memory

String* ps = new String("Hello"); ... delete ps; //编译器转化为 String::~String(ps); //析构函数 operator delete(ps); //释放内存 其内部调用free(ps)


2.21 重载 ::operator new,::operator delete

::operator new[], ::operator delete[]

这些都是全局的函数,重载的是全局的函数,小心影响无远弗界

重载member operator new/delete,做一个内存池

重载member operator new[ ]/delete[ ],做一个内存池


2.22 示例


2.23 重载new(),delete()


2.24 Basic_String使用new(extra)扩充申请量

Rep用于存储计数的

template<...> 
class basic_string 
{ 
private: 
struct Rep
{ 
    ... 
    void release() { if(--ref==0) delete this; } 
} 
inline static void* operator new(size_t, size_t); 
inline static void operator delete(void*); 
inline static Rep* create(size_t); 
... 
}; 
template<class charT, chass 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, chass traits, class 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, chass traits, class Allocator> 
inline void* basic_string<charT,traits,Allocator>::Rep:: operator delete(void* ptr) 
{ 
Allocator::deallocate(ptr,sizeof(Rep)+ reinterpret_cast<Rep *>(ptr)->res* sizeof(charT)); }


版权声明:本文为harveyhan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。