這門課主要偏重于泛型編程(generic programming)以及底層對象模型(this,vptr,vtbl,多態(tài)(polymorphism)等)卒废。
首先提到的類成員函數(shù)是轉(zhuǎn)換函數(shù)(conversion function)和隱式單參數(shù)構(gòu)造函數(shù)(non-explicit one argument constructor).如講義中提到的下面例子:
#include <iostream>
using namespace std;
class Fraction
{
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) {
if(m_denominator != 0)
{
int gcd_val = gcd(m_numerator, m_denominator);
if(gcd_val != 1)
{
m_numerator = m_numerator / gcd_val;
m_denominator = m_denominator / gcd_val;
}
}
}
#if 1
operator double() const {
return (double)(m_numerator/m_denominator);
}
#endif
static int gcd(int a, int b)
{
if(b == 0)
return a;
else
return gcd(b, a%b);
}
Fraction operator+(const Fraction& f) {
return Fraction(m_numerator * f.m_denominator + m_denominator * f.m_numerator, m_denominator * f.m_denominator);
}
friend ostream& operator<<(ostream& os, const Fraction& f);
private
int m_numerator; //分子
int m_denominator; //分母
};
ostream& operator<<(ostream& os, const Fraction& f)
{
os << f.m_numerator << '/' << f.m_denominator;
return os;
}
int main(void)
{
Fraction f1(3,5);
Fraction f2 = f1 + 4;
cout << f1 << " " << f2 << endl;
return 0;
}
這個(gè)類中double()是conversion function,此處構(gòu)造函數(shù)是隱式單參數(shù)構(gòu)造函數(shù)甩卓。
這種情況下凝垛,有可能將將f轉(zhuǎn)換成double,然后將得到double與4相加,得到結(jié)果轉(zhuǎn)換成Fraction.也可能對4使用構(gòu)造函數(shù)轉(zhuǎn)換成Fraction,然后將f和構(gòu)造生成的Fraction對象相加恃锉,將最終結(jié)果賦給f2.這時(shí)候會產(chǎn)生二義性隔盛。編譯時(shí)有下面的錯(cuò)誤信息:
error: use of overloaded operator '+' is ambiguous (with operand types 'Fraction' and 'int')
如果將double()函數(shù)的定義注釋掉就可以正常編譯執(zhí)行,并能得到下面輸出信息:
3/5 23/5
另一個(gè)有趣的類是智能指針(shared_ptr朗儒、unique_ptr等)颊乘,智能指針是像指針的類,其定義如下:
template <class T>
{
public:
??? T& operator*() const { return *px; }
??? T* operator->() const { return px; }
??? shared_ptr(T* p) : px(p) {}
private:
??? T *px;
....
}
struct Foo
{
...
??? void method(void) {...}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();
對于這里的sp對象調(diào)用*或者->醉锄,實(shí)際作用的是該對象內(nèi)的指針成員變量px.
另外sp->method()中sp->對應(yīng)于px,那么貌似px后直接跟隨method(),感覺會有奇怪疲牵。而事實(shí)上sp后會一直帶有隱式的->,所以仍可以調(diào)用px->method函數(shù)。
還有一個(gè)比較有趣的是迭代器對象榆鼠,例如__list_iterator對象如下:
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;
? 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; } //前置++操作符
};
template<class T>
struct __list__node{
? void *prev;
? void *next;
? T data;
};
list<Foo>::iterator ite;
*ite;
ite->method();
//相當(dāng)于調(diào)用(&(*node).data)->method();
//相當(dāng)于調(diào)用(*node).data.method()
另外有意思的一種類是類似于函數(shù)的類(function-like class),一個(gè)簡單的小例子,代碼如下:
#includeusing namespace std;
class A
{
public:
A(int m=0,int n=0):x(m), y(n) {}
int operator()()
{
return x + y;
}
int operator()(int z)
{
return x + y + z;
}
int operator() (int z, int r)
{
return x + y + z + r;
}
private:
int x;
int y;
};
int main(void)
{
A a(3,4);
cout << a() << endl;
cout << a(2) << endl;
cout << a(2,3) << endl;
return 0;
}
在使用時(shí)可以創(chuàng)建對象亥鸠,然后像使用函數(shù)一樣給這個(gè)對象傳遞參數(shù)妆够,就會調(diào)用重載()的函數(shù)。
這節(jié)課中提到模板類重載()操作符的情況负蚊,課堂中提到下面例子:
template <class T>
struct identity {
const T& operator()(const T& x) const { return x; }
};
template <class T>
{
struct plus T operator() (const T&x, const T& y) const { return x + y; }
};
這兩個(gè)模板類在重載()操作符時(shí)會隱含的用到下面這兩個(gè)特殊的基類:
template <class Arg, class Result> struct unary_function{
? typedef Arg_argument_type;
? typedef Result result_type;
};
template <class Arg1, class Arg2, class Result> struct binary_function {
? typedef Arg1 first_argument_type;
? typedef Arg2 second_argument_type;
? typedef Result result_type;
};
這兩個(gè)基類參數(shù)分別為1個(gè)或者2個(gè)神妹。
我查看了STL網(wǎng)頁上關(guān)于Functors?的介紹,這里面提到家妆,所有的函數(shù)鸵荠、函數(shù)指針以及重載()操作符的類都可以被稱為functor(或者function object)。而在STL算法中functor只需要零個(gè)伤极、一個(gè)或者兩個(gè)參數(shù)蛹找,分別用generator、unary_function哨坪、binary_function庸疾,這三個(gè)functor對應(yīng)于函數(shù)f(), f(x)和f(x,y).
另外,有趣的是当编,c++11中認(rèn)為unary_function和binary_function已經(jīng)過時(shí)了届慈,不建議使用,貌似c++標(biāo)準(zhǔn)委員會建議c++17將它們刪除忿偷,stackoverflow上面有討論的頁面?金顿,不過我沒看懂。
課堂中還講到namespace,寫個(gè)測試小函數(shù):
#includeusing namespace std;
namespace yy1
{
int sum(const int& x,const int& y)
{
return x + y;
}
}
int main(void)
{
cout << yy1::sum(4,5) << endl;
return 0;
}
還講到了類模板和函數(shù)模板鲤桥。
類模板定義如下:
template <typename T>
class Point
{
? public:
??? Point(T m = 0, T n = 0):x(m), y(n) {}
??? T DistanceFromOrigin() {return sqrt(x * x + y * y); }
? private:
??? int x;
??? int y;
};
使用方式如下:
Point<double> p(3,4);
而函數(shù)模板定義如下:
template <class T> inline const T& min(const T& a, const T&b)
{
? return b < a ? b : a;
}
在使用min函數(shù)時(shí)揍拆,會對T的類型進(jìn)行推導(dǎo)。
另外還有類成員函數(shù)模板茶凳,其用法跟函數(shù)模板類似礁凡。
對于模板高氮,還可以用template specialization,講義中提到下面的例子:
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; }
};
還有部分特例化(partial specialization),講義中例子如下顷牌;
template <typename T, typename Alloc=...> class vector
{
? ...
};
template <typename Alloc = ...>? class vector<bool, Alloc>
{ ... }
還有比較有趣的模板模板參數(shù)(template template parameter), 看下面例子:
template<typename T, template<typename T> class SmartPtr>
class XCls
{
? private:
??? SmartPtr<T> sp;
? public:
??? XCls:sp(new T) {}
};
使用上面定義的例子如下:
XCls<string, shared_ptr> p1;
可以編寫在程序中輸出__cplusplus的值來判斷當(dāng)前使用的c++標(biāo)準(zhǔn)剪芍。
我當(dāng)前使用的是clang 3.4,如果使用默認(rèn)編譯選項(xiàng)窟蓝,那么輸出結(jié)果是199711 對應(yīng)c++0x
如果添加-std=c++11,那么輸出結(jié)果是201103? 對應(yīng)c++11
如果添加-std=c++1y罪裹,那么輸出結(jié)果是201305.
c++11中支持auto,可以自動推斷變量類型运挫,并且c++11支持范圍操作状共,看下面例子:
vector v = {1,2,3,4,5};
for(auto item: v)
cout << item << endl;
另外,c++中谁帕,如果輸出對象和reference的大小和地址峡继,其值是相等的。
但實(shí)際上reference應(yīng)該是用指針來實(shí)現(xiàn)的匈挖。reference通常不直接定義碾牌,主要用于參數(shù)和返回類型。
類之間的關(guān)系有繼承(inheritance)和復(fù)合(composition)以及二者的結(jié)合儡循。
而其構(gòu)造函數(shù)是從內(nèi)到外執(zhí)行舶吗,析構(gòu)函數(shù)是從外到內(nèi)執(zhí)行。
本節(jié)有一個(gè)有趣的習(xí)題择膝,就是對象內(nèi)存布局誓琼,看下面例子:
#include <iostream>
using namespace std;
class Base
{
int x;
char y;
public:
void f1() const{
cout << "f1" << endl;
}
virtual void g1() const {}
};
class Derived:public Base
{
char z;
public:
void s1() const{
cout << "s1" << endl;
}
virtual void g1() const{}
};
int main(void)
{
Base b1,b2;
b1.f1();
Derived d1,d2;
d1.s1();
return 0;
}
對上面程序(名稱為object_model.cpp)進(jìn)行編譯,:
clang++ object_model.cpp? -g -o object_model
nm 命令查看object_model,然后能找到其中幾個(gè)函數(shù)地址:
08048880 W _ZNK4Base2f1Ev
08048950 W _ZNK4Base2g1Ev
08048940 W _ZNK7Derived2g1Ev
08048900 W _ZNK7Derived2s1Ev
使用c++filt 對demange這幾個(gè)函數(shù)名稱肴捉,得到下面信息:
mangled name?????? demangled name
_ZNK4Base2f1Ev? Base::f1() const
_ZNK4Base2g1Ev Base::g1() const
_ZNK7Derived2g1Ev Derived::g1() const
_ZNK7Derived2s1Ev Derived::s1() const
使用gdb調(diào)試object_model,然后在main函數(shù)末尾處打斷點(diǎn):
(gdb) p &b1
$1 = (Base *) 0xffffced8
(gdb) p &(b1.x)
$2 = (int *) 0xffffcedc
(gdb) p &(b1.y)
$3 = 0xffffcee0 "p\211\004\b"
(gdb) p b1.f1
$4 = {void (const Base * const)} 0x8048880
(gdb) p b1.g1
$5 = {void (const Base * const)} 0x8048950
(gdb) info vtbl b1
vtable for 'Base' @ 0x8048a64 (subobject @ 0xffffced8):
[0]: 0x8048950
類似地腹侣,對b2也執(zhí)行類似的操作,結(jié)果如下:
(gdb) p &b2
$6 = (Base *) 0xffffcec8
(gdb) p &(b2.x)
$7 = (int *) 0xffffcecc
(gdb) p &(b2.y)$8 = 0xffffced0 "p\211\004\b\200\206\004\bd\212\004\b\364\277\200Ap\211\004\b"
(gdb) p b2.f1
$9 = {void (const Base * const)} 0x8048880
(gdb) p b2.g1
$10 = {void (const Base * const)} 0x8048950
(gdb) info vtbl b2
vtable for 'Base' @ 0x8048a64 (subobject @ 0xffffcec8):
[0]: 0x8048950
再分別打印b1和b2這兩個(gè)變量:
(gdb) p b1
$12 = {_vptr$Base = 0x8048a64, x = 1098956788, y = 112 'p'}
(gdb) p b2
$13 = {_vptr$Base = 0x8048a64, x = 1098956788, y = 112 'p'}
(gdb) p sizeof(b1)
$14 = 12
(gdb) p sizeof(b2)
$15 = 12
可以畫出b1內(nèi)存布局如下:??????????
_______________??????????????????? ---------------------------(vtbl)????????
|? vptr(0x8048a64)? ? ? ? ? | --------> |? ? ? ? 0x8048950?? () ? ?? |? ? ? ? ? ?
________________????????????????? __________________??????
|? x (4 byte) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? |
__________________
| y (1 byte)? ? ? ? ? ? ? ? ? ? ? ? ? ? |
___________________
|? padding(3 bytes)????????? ? ?? |
___________________
b2的vptr和vtbl中保存的值跟b1完全相等齿穗。但是x和y的地址完全不同筐带。
使用gdb打印d1和d2相關(guān)信息:
(gdb) p &d1
$16 = (Derived *) 0xffffceb8
(gdb) p &(d1.x)
$17 = (int *) 0xffffcebc
(gdb) p &(d1.y)
$18 = 0xffffcec0 "\001"
(gdb) p d1.f1
$19 = {void (const Base * const)} 0x8048880
(gdb) p d1.s1
$20 = {void (const Derived * const)} 0x8048900
(gdb) p d1.g1
$21 = {void (const Derived * const)} 0x8048940
(gdb) info vtbl d1
vtable for 'Derived' @ 0x8048a30 (subobject @ 0xffffceb8):
[0]: 0x8048940
(gdb) p &d2
$22 = (Derived *) 0xffffcea8
(gdb) p &(d2.x)
$23 = (int *) 0xffffceac
(gdb) p &(d2.y)
$24 = 0xffffceb0 "\b\206\377\367`\235\004\b0\212\004\b\273\211\004\b\001"
(gdb) p d2.f1
$25 = {void (const Base * const)} 0x8048880
(gdb) p d2.s1
$26 = {void (const Derived * const)} 0x8048900
(gdb) p d2.g1
$27 = {void (const Derived * const)} 0x8048940
(gdb) info vtbl d2
vtable for 'Derived' @ 0x8048a30 (subobject @ 0xffffcea8):
[0]: 0x8048940
d1內(nèi)存布局如下(12 bytes):
_______________? ? ? ? ? ? ? ? ? ? ---------------------------(vtbl)
|? vptr(0x8048a30)? ? ? ? | --------> |? ? ? ? 0x8048940? (Derived::g1())? ? ? |
________________????????????????? __________________
|? x ?? (4 bytes)? ? ? ? ? ? ? ? ? ?? |
__________________
| y???????? (1 bytes) ????? ? ? ? ? ? ?? |
___________________
|? z?????? (1 byte)??????????????????? |
____________________
|? padding(2 bytes)? ? ? ? ? ? ? |
___________________
d2中vptr和vtbl的值也是完全相等的,但x缤灵、y和z的地址不相等伦籍。
可以使用clang打印出對象的布局信息,使用下面命令(可能需要先將std以及cout注釋掉):
clang -cc1? -fdump-record-layouts object_model.cpp
生成一個(gè)object_model.cpp.002t.class文件腮出,里面有類似下面的信息(要先用c++filt對符號進(jìn)行轉(zhuǎn)換得到下面信息):
Vtable for Base
Base::vtable for Base: 3u entries
0? ? (int (*)(...))0
4? ? (int (*)(...))(& typeinfo for Base)
8? ? Base::g1
Class Base
size=12 align=4
base size=9 base align=4
Base (0x7f23ee353a80) 0
vptr=((& Base::vtable for Base) + 8u)
Vtable for Derived
Derived::vtable for Derived: 3u entries
0? ? (int (*)(...))0
4? ? (int (*)(...))(& typeinfo for Derived)
8? ? Derived::g1
Class Derived
size=12 align=4
base size=10 base align=4
Derived (0x7f23ee353e70) 0
vptr=((& Derived::vtable for Derived) + 8u)
Base (0x7f23ee353ee0) 0
primary-for Derived (0x7f23ee353e70)
按照這個(gè)生成信息可以知道帖鸦,clang 3.4采用4字節(jié)對齊,并且虛函數(shù)表中有三項(xiàng):
第一項(xiàng)是0
第二項(xiàng)是其type info
第三項(xiàng)才是其中的虛函數(shù)
g++中也有類似命令:
g++ -fdump-class-hierarchy object_model.cpp
在review其它同學(xué)的筆記時(shí)胚嘲,有提到new/delete和malloc/free的區(qū)別作儿。new/delete在自由存儲區(qū),而malloc/free是在堆上馋劈,new/delete是類型安全的攻锰,而malloc/free不是類型安全晾嘶。在c++中只應(yīng)當(dāng)使用new/delete,盡量不要使用malloc/free.雖然二者有free storage和堆的差別,但實(shí)際上跟編譯器實(shí)現(xiàn)有關(guān)娶吞,可能這兩個(gè)區(qū)域位于相同的區(qū)域垒迂,也可能位于不同的區(qū)域。關(guān)于二者的比較可以參看下面的文章:
http://www.cnblogs.com/jiayouwyhit/archive/2013/08/06/3242124.html
http://stackoverflow.com/questions/240212/what-is-the-difference-between-new-delete-and-malloc-free