C++ Programming(part II), and Object Model.
侯捷
笔记
前言
应具备的基础
- 是上一篇博文“面向对象程序设计”的续集
- 本文将探讨上文未讨论的主题
目标
- 在先前培养正规、大器的编程素养上,继续探讨更多技术。
- 泛型编程(Generic Programming)和面向对象编程(Object-Oriented Programming)虽然分属不同思维,但它们正是C++的技术主线。本文也讨论template(模板)。
- 深入探索面向对象之继承关系(inheritance)所形成的对象模型(Object Model),包括隐藏于底层的this指针,vptr指针(虚指针),vtbl(虚表),virtual mechanism(虚机制),以及虚函数(virtual functions)造成的polymorphism(多态)效果。
将获得的代码
Test-Cpp.cpp
C++编译器
- 编译(compile)
- 连接(link)
conversion function, 转换函数
1 | class Fraction |
使用:1
2Fraction f(3, 5);
double d = 4 + f;//调用operator double()将f转为0.6
non-explicit-one-argument ctor
1 | class Fraction |
使用:1
2Fraction f(3, 5);
Fraction d2 = f + 4;//调用non-explicit ctor将4转为Fraction(4, 1),然后调用operator+
conversion function vs. non-explicit-one-argument ctor
1 | class Fraction |
使用:1
2Fraction f(3, 5);
Fraction d2 = f + 4;//[ERROR]ambiguous 二义
explicit-one-argument ctor
1 | class Fraction |
使用:1
2Fraction f(3, 5);
Fraction d2 = f + 4;//[ERROR]conersion from 'double' to 'Fraction' requested
conversion function, 转换函数
proxy1
2
3
4
5
6
7
8
9
10
11template<class Alloc>
class vector<bool, Alloc>
{
public:
typedef __bit_reference reference;
protected:
reference operator[] (size_type n)
{
return *(begin() + difference_type(n));
}
...1
2
3
4
5
6
7
8struct __bit_reference
{
unsigned int* p;
unsigned int mask;
...
public:
operator bool() const {return !(!(*p & mask)); }
...
pointer-like classes, 关于智能指针
1 | template<class T> |
使用:1
2
3
4
5struct Foo
{
...
void method(void) {......}
};1
2
3
4
5shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();
相当于1
px->method();
pointer-like classes, 关于迭代器
1 | template<class T, class Ref, class Ptr> |
使用:1
2
3
4
5
6
7list<Foo>::iterator ite;
...
*ite;//获得一个Foo object
ite->method();
//意思是调用Foo::method()
//相当于(*ite).method();
//相当于(&(*ite))->method();
funciton-like classes, 所谓仿函数
1 | template <class T> |
1 | template <class T1, class T2> |
标准库中仿仿函数的奇特模样
1 | template <class T> |
1 | template <class T> |
标准库中,仿函数所使用的奇特的base classes
1 | template <class Arg, class Result> |
less
namespace经验谈
1 | using namespace std; |
使用:1
2
3int main(int argc, char** argv)
jj01::test_member_template();
jj02::test_template_template();
class template, 类模板
1 | template<typename T> |
使用:1
2
3
4
5{
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
...
}
function template, 函数模板
1 | stone r1(2, 3), r2(3, 3), r3; |
编译器会对function template进行实参推导(argument deduction)1
2
3
4
5template <class T>
inline const T& min(const T& a, const T& b)
{
return b < a ? b : a;
}
实参推导的结果,T为stone,于是调用stone::operator<1
2
3
4
5
6
7
8
9
10
11class 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;
};
member template, 成员函数
1 | template <class T1, class T2> |
1 | class Base1{}; |
1 | pair<Derived1, Derived2>p; |
1 | pair<Base1, Base2>p2(pair<Derived1, Derived2>()); |
1 | template<typename _Tp> |
1 | Base1* ptr = new Derived1;//up-cast |
specialization, 模板特化
【注】特化反义词:泛化
泛化1
2template <class Key>
struct hash{ };
特化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17template<>
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; }
};
使用:1
cout << hash<long>() (1000);
泛化又叫full specialization,全泛化,对应偏特化。
patial specialization, 模板偏特化——个数的偏
1 | template<typename T, typename Alloc=...> |
绑定1
2
3
4template<typename Alloc=...>
class vector<bool, Alloc>
{
...
patial specialization, 模板偏特化——范围的偏
1 | template <typename T> |
【注】上下的T不是一个T1
2
3
4
5template <typename T>
class C<T*>
{
...
};
这样写也可以1
2
3
4
5template <typename U>
class C<U*>
{
...
};
使用:1
2C<string> obj1;
C<string*> obj2;
template template parameter, 模板模板参数
1 | template<typename T, |
1 | template<typename T> |
1 | XCls<string, list> mylst1;//错误 |
1 | template<typename T, |
1 | XCls<string, shared_ptr> p1; |
这不是template template parameter
1 | template <class T, class Sequence = deque<T>> |
使用1
2stack<int> s1;
stack<int, list<int>> s2;
关于C++标准库
容器
Sequence containers
array
vector
deque
forward_list
list
Container adaptors
stack
queue
priority_queue
Associative containers
set
multiset
map
multimap
Unordered associative con
unordered_set
unordered_multiset
unordered_map
unordered_multimap
算法
…
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
Merge
merge
inplace_merge
includes
set_union
set_intersection
set_difference
set_symmetric_difference
…
推书:Algorithms + Data Structures = Programs(Niklaus Wirth)
确认支持C++11: macro __cplusplus
测试:
VS20121
2
3
4
5
6
7
8
9
using namespace std;
int main()
{
cout<<__cplusplus<<endl;
return 0;
}
Dev-C++ 51
2
3
4
5
6
int main()
{
std::cout<<__cplusplus;
}
如果是199711,则不支持C++11,需修改编译器
如果是201103,则支持C++11
variadic templates(since C++11) 数量不定的模板参数
1 | void print() |
Inside variadic templates, sizeof…(arg) yields the number of arguments
…就是一个所谓的pack(包)
用于template parameters, 就是template parameters pack(模板参数包)
用于function parameter types, 就是function parameter types pack(函数参数类型包)
用于function parameters, 就是function parameters pack(函数参数包)
使用:1
print(7.5, "hello", bitset<16>(377), 42);
结果:1
2
3
47.5
hello
0000000101111001
42
auto(since C++11)
过去:1
2
3
4list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
现在:1
2
3list<string> c;
...
auto ite = find(c.begin(), c.end(), target);
错误:1
2
3
4list<string> c;
...
auto ite;//错误
ite = find(c.begin(), c.end(), target);
ranged-base for(since C++11)
1 | for(decl : coll) |
1 | for(int i : {2, 3, 5, 7, 9, 13, 17, 19}) |
1 | vector<double> vec; |
reference
1 | int x=0; |
从内存上看,
注意:
- sizeof(r) == sizeof(x)
- &x = &r;
object和其reference的大小相同,地址也相同(全都是假象)
Java里头所有变量都是reference
1 | typedef struct Stag{int a, b, c, d;} S; |
object和其reference的大小相同,地址也相同(全都是假象)
reference的常见用途
1 | void func1(Cls* pobj) {pobj->xxx();} |
reference通常不用于声明变量,而用于参数类型(parameters type)和返回类型(return type)的描述。
以下被视为”same signature”(所以二者不能同时存在):1
2double imag(const double& im) {...}
double imag(const double im) {...} //Ambiguity
【注】imag(const double& im)为signature, 不含return type.
imag(const double& im)后面可以加const, const是函数签名的一部分。
所以imag(const double& im)和imag(const double& im) const两个函数可以并存。
对象模型(Object Model):关于vptr 和 vtbl
1 | class A |
1 | class B:public A |
1 | class C:public B |
对象模型(Object Model):关于this
Template Method
再谈const
const object(data members不得变动) non-const object(data members可变动)
const member functions
(保证不更改data members) √ √
non-const member functions
(不保证data members不变) × √
当成员函数的const和non-const版本同时存在,const object只会(只能)调用const版本,non-const object只会(只能)调用non-const版本。
1 | const String str("hello world"); |
如果当初设计string::print()时未指明const,那么上行便是经由const object调用non-const member function,会出错。此非所愿。
non-const member functions可调用const member functions,反之则不行,会引发:1
(VC)error C2662:cannot convert 'this' pointer from 'const class X' to 'class X &'.Conversion loses qualifiers
class template std::basic_string<…>有如下两个member functions:1
2
3
4
5
6
7charT
operator[](size_type pos) const
{....../*不必考虑COW*/}
reference
operator[](size_type pos)
{....../*必须考虑COW*/}
COW:Copy On Write
对象模型(Object Model):关于Dynamic Binding
动态绑定三个条件:
- 通过指针
- 虚函数
- 向上转型
再谈new和delete
::operator new, ::operator delete, ::operator new[], ::operator delete[]
重载member operator new/delete
重载member operator new[]/delete[]
和上图的区别在于多了一个[]1
2
3
4
5
6class Foo
{
public:
void* operator new[](size_t);
void operator delete[](void*, size_t);
};
示例, 接口
int 4字节,long 4字节,string(里面是个指针)4字节
有虚函数就多一个指针(12+4=16)
Foo[5] 数组,有5个,12*5=60,第一个记录有5个元素,这个记录的size为4,60+4=64
重载new(), delete()
我们可以重载class member operator new(),写出多个版本,前提是每一个版本的声明都必须有独特的参数列,其中第一参数必须是size_t,其余参数以new所指定的placement arguments为初值。出现于new(……)小括号内的便是所谓placement arguments。
1 | Foo* pf = new(300, 'c') Foo; |
我们也可以重载class member operator delete()(或称此为placement operator delete),写出多个版本,但它们绝不会被delete调用。只有当new所调用的ctor抛出exception,才会调用这些重载版的operator delete()。它只可能这样被调用,主要用来归还未能完全创建成功的object所占用的memory。
示例
1 | class Foo |
测试代码:1
2
3
4
5
6
7
8
9Foo start;
Foo* p1 = new Foo;
Foo* p2 = new(&start) Foo;
Foo* p3 = new(100) Foo;
Foo* p4 = new(100,'a') Foo;
Foo* p5 = new(100) Foo(1);//ctor抛出异常
Foo* p6 = new(100,'a') Foo(1);
Foo* p7 = new(&start) Foo(1);
Foo* p8 = new Foo(1);
ctor抛出异常,但G4.9没调用operator delete(void*, long),但G2.9确实调用了。
即使operator delete(…)未能一一对应于operator new(…),也不会出现任何报错。意思是:放弃处理ctor发出的异常。