//define误用举例
#define MAX(a, b) a > b ? a : b
int a = 5, b = 0;
MAX(++a, b) //a++调用2次
MAX(++a, b+10) //a++调用一次
然而,了解宏的机制以后,我们也可以用宏实现特殊的技巧。例如:C++反射,TEST
using namespace std;
typedef void *(*register_fun)();
class CCFactory{
public:
static void *NewInstance(string class_name){
auto it = map_.find(class_name);
if(it == map_.end()){
return NULL;
}else
return it->second();
}
static void Register(string class_name, register_fun func){
map_[class_name] = func;
}
private:
static map<string, register_fun> map_;
};
map<string, register_fun> CCFactory::map_;
class Register{
public:
Register(string class_name, register_fun func){
CCFactory::Register(class_name, func);
}
};
#define REGISTER_CLASS(class_name); \
const Register class_name_register(#class_name, []()->void *{return new class_name;});
尽量使用const来修饰函数名和参数变量名
尽量使用const来修饰类名
void function(classA test); //classA为自定义的类型
这样使用值传参的缺点:
建议:
void function(const classA &s);
优点:
原则:
将static对象放入一个函数
Fuck& fuck(){
static Fuck f;
return f;
}
如果类中没有定义,程序却调用了,编译器会产生一些函数
一个 default 构造函数
一个 copy 构造函数
一个 copy assignment 操作符
一个析构函数(non virtual)
如果自己构造了带参数的构造函数,编译器不会产生default构造函数
base class如果把拷贝构造函数或者赋值操作符设置为private,不会产生这两个函数
含有引用成员变量或者const成员变量不产生赋值操作符
class Fuck{
private:
std::string& str;//引用定义后不能修改绑定对象
const std::string con_str;//const对象定义后不能修改
};
将默认生成的函数声明为private,或者C++ 11新特性"=delete"
class Uncopyable{
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator= (const Uncopyable&);
}
构造函数可以抛出异常,析构函数不能抛出异常。
因为析构函数有两个地方可能被调用。一是用户调用,这时抛出异常完全没问题。二是前面有异常抛出,正在清理堆栈,调用析构函数。这时如果再抛出异常,两个异常同时存在,异常处理机制只能terminate().
构造函数抛出异常,会有内存泄漏吗?
不会
try {
// 第二步,调用构造函数构造对象
new (p)T; // placement new: 只调用T的构造函数
}
catch(...) {
delete p; // 释放第一步分配的内存
throw; // 重抛异常,通知应用程序
}
构造和析构过程中,虚表指针指向的虚表在变化。调用的是对应虚表指针指向的函数。
int x,y,z;
x=y=z=15;同样有趣的是,赋值采用右结合律,
x=(y=(z=15));
Widget&operator=(const Widget&rhs)//返回类型是个reference,
{return*this;/返回左侧对象}
* 在operator= 里处理自我赋值
Widget& Widget::operator== (const Widget& rhs){
if(this == &rhs) return *this
···
}
用户可能需要原始资源作为参数传入某个接口。有两种方式:
new和delete对应;new []和delete []对应
//前面还分配了4个字节代表数组的个数
int *A = new int[10];
//前面分配了8个字节,分别代表对象的个数和Object的大小
Object *O = new Object[10];
调用std::make_shared,而不要调用new,防止new Obeject和传入智能指针的过程产生异常
process(new Widget, priority);
//其实这样也可以,独立的语句
shard_ptr<Widget> p(new Widget);
process(p, priority);
宁以pass-by-refrence-to-const替换pass-by-value
1.尽量以pass-by-reference-to-const替换pass-by-value,比较高效,并且可以避免切割问题
2.以上规则并不使用内置类型,以及STL迭代器,和函数对象。它们采用pass-by-value更合适(其实采用pass-by-reference-to-const也可以)
对象以by value的方式传递,其实际意义是由该对象的copy constructor决定的。这可能会使pass-by-value成为成本很高的动作。
以pass by reference,还可以避免所谓的“切割slicing问题”,又称为“upcasting问题”。详见《Thingking in C++》P629
pass by reference是一件美妙的事情,但会导致某些复杂性。最知名的问题就是aliasing(别名问题),见条款17。某些情况下必须pass by value。references的底层几乎都是指针完成,所以passing by reference通常意味着传递的是指针。如果有个小对象,例如一个int,那么pass by value可能比pass by reference的效率更高一些。
必须返回对象时,别妄想返回其reference
1.不要返回pointer或者reference指向一个on stack对象(被析构)
2.不要返回pointer或者reference指向一个on heap对象(需要用户delete,我觉得必要的时候也不是不可以)
3.不要返回pointer或者reference指向local static对象,却需要多个这样的对象(static只能有一份)
将成员变量申明为private
1.切记将成员变量申明为private
2.protected并不比public更有封装性(用户可能继承你的base class)
作者说多一个成员函数,就多一分破坏封装性,好像有点道理,但是我们都没有这样遵守。直接写member函数方便一些。
如果调用member函数,就使得第一个参数的类失去一次类型转换的机会。
class Obj{
Obj(const Obj&){//深拷贝}
Obj& operator= (const Obj&){深拷贝
private:
OtherClass *p;
};
如果提供一个member swap,也该提供一个non-member swap用来调用前者
调用swap时应该针对std::swap使用using声明式,然后调用swap不带任何"命名空间修饰”
void doSomething(Obj& o1, Obj& o2){
//这样可以让编译器自己决定调用哪个swap,万一用户没有实现针对Obj的swap,还能调用std::swap
using std::swap;
swap(o1, o2);
}
【尽量使用初始化列表来代替赋值】
const members和reference members只能被初始化,不能够被赋值(assigned)。这个时候,如果在构造函数中要对其初始化时必须用member initialization list。 另外,从效率方面考虑,也建议用以initialization动作取代assignment动作。
例外的情况:
class members系以它们在class内的声明次序来初始化,和它们在member initialization list中出现的次序完全无关。基类的成员变量永远在继承类成员变量之前被初始化,所以如果运用了继承,你应该在member intialization lists起始处列出base class的初始设定值。
结论是:对象被初始化时,如果你希望确实掌握真正发生了什么事,请以class内的members声明次序,将各个memebers列于initialization list中。
member functions和non-member functions的区别是:member functions可以是虚函数,而non-member functions不可以。
在一个类中,只要能够避免friend函数,就应该尽量避免,“因为就像真实世界一样,朋友带来的麻烦常常多于其价值。”:)_
C语言推荐在函数开始的时候定义所有变量(最开始的C语言编译器要求,现在并不需要),C++推荐在使用对象前才定义对象
简单说,就是成员函数返回指针或者非const引用不要指向成员变量,这样会破坏封装性
"异常安全函数"承诺即使发生异常也不会有资源泄漏。在这个基础下,它有3个级别
"强烈保证"往往可以通过copy-and-swap实现,但是"强烈保证"并非对所有函数都具有实现意义
//我反正从来没有这样写过
void doSomething(Object& obj){
Object new_obj(obj);
new_obj++;
swap(obj, new_obj);
}
这里插播一个C++处理定义的重要原则,一处定义原则:
全局变量,静态数据成员,非内联函数和成员函数只能整个程序定义一次
类类型(class,struct,union),内联函数可以每个翻译单元定义一次
inline应该限制在小的,频繁调用的函数上
inline只是给编译器的建议,编译器不一定执行
其实就是使用前置声明,下面有个需要注意的点
//Obj.h
class ObjImpl;
class Obj{
public:
private:
std::shared_ptr<ObjImpl> pObjImpl;
};
//上面的写法会报错,因为编译器会再.h文件里面产生默认的析构函数,
//析构函数要调用ObjImpl的析构函数,然后我们现在只有声明式,不能调用ObjImpl的实现。
//下面的实现才是正确的
//Obj.h
class ObjImpl;
class Obj{
public:
//声明
~Obj();
private:
std::shared_ptr<ObjImpl> pObjImpl;
};
//Obj.cpp
//现在可以看到ObjImpl的实现
#include
Obj::~Obj(){
}
对于STL的对象不需要前置声明。
public继承意味着is-a。适用于base class身上的每一个函数也一定适用于derived class。
子作用域会遮掩父作用域的名称。一般来讲,我们可以有以下几层作用域
注意:遮掩的是上一层作用域的名称,重载(不同参数)的函数也会直接遮掩
class Base{
public:
void f1();
}
class Drive{
public:
//会遮掩f1(),子类并没有继承f1()
void f1(int);
}
Drive d;
d.f1(); //错误
d.f1(3); //正确
可以通过using声明式或者inline转交解决这一问题
class Base{
public:
void f1();
}
//using 声明式
class Drive{
public:
using Base::f1;
void f1(int);
}
//inline转交
class Drive{
public:
void f1(){
Base::f1();
}
void f1(int);
}
在编写自己的 class 时,你应该明白提供下面 3 种类型函数的理由
何时提供 pure virtual function?
要求派生类只继承接口时,提供纯虚函数。【就像下单函数一样,其作用就是提供接口让你来重写】
class MyType
{
public:
//派生类只继承接口
vitrual void fun() const = 0;
};
何时提供 virtual function?
要求派生类只继承接口和缺省实现时,提供虚函数。
class MyType
{
public:
//派生类只继承接口
vitrual void fun() const
{
}
};
何时提供 no-virtual function?
要求派生类只继承接口的强制实现时,提供非虚函数。
class MyType
{
public:
//派生类只继承接口
void fun() const
{
}
};
原则
non-virtual interface:提供非虚接口
class Object{
public:
void Interface(){
···
doInterface();
···
}
private/protected:
virtual doInterface(){}
}
优点:
我们知道,程序库的优势之一是库版本升级,只要保证借口的一致性,用户不用修改任何代码。
一般一个设计完好的程序库都会提供一份C语言接口,为什么呢,我们来看看C++ ABI有哪些脆弱性。
//Object.h
class Object{
public:
···
virtual print(){}//第3个虚函数
···
}
//用户代码
int main(){
Object *p = new Object;
p->print(); //编译器:vptr[3]()
}
//如果加了虚函数,用户代码根据偏移量找到的是newfun函数
//Object.h
class Object{
public:
···
virtual newfun()//第3个虚函数
virtual print(){}//第4个虚函数
···
}
C++没有为name mangling制定标准。例如void fun(int),有的编译器定为fun_int_,有的编译器指定为fun%int%。
因此,C++接口的库要求用户必须和自己使用同样的编译器(这个要求好过分)
例如struct和class。编译阶段,编译器将struct或class的对象对成员的访问通过偏移量来实现
class Object{
public:
void Interface(){
···
doInterface();
···
}
private/protected:
std::function<void()> doInterface;
}
用另外一个继承体系替代
class Object{
public:
void Interface(){
···
p->doInterface();
···
}
private/protected:
BaseInterface *p;
}
class BaseInterface{
public:
virtual void doInterface(){}
}
记住就行
class Base{
public:
virtual void print(int a = 1) {cout <<"Base "<< a <<endl;};
int a;
};
class Drive : public Base{
public:
void print(int a = 2){cout << "Drive " << a <<endl;}
};
int main(){
Base *b = new Drive;
b->print(); // vptr[0](1)
}
//Drive 1
这个也是什么时候使用继承,什么时候使用复合。复合代表使用了这个对象的某些方法,但是却不想它的接口入侵。
C++ 设计者在设计这门语言要求所有的对象必须要有不同的地址(C语言没有这个要求)。C++编译器的实现方式是给让空类占据一个字节。
class Base{
public:
void fun(){}
}
//8个字节
class Object{
private:
int a;
Base b;
};
//4个字节
classObject : private Base{
private:
int a;
}
首先我们来了解一下多重继承的内存布局。
//包含A对象
class A{
};
//包含A,B对象
class B:public A{
};
//包含A,C对象
class C:public A{
};
//包含A,A,B,C,D对象
class D:public B, public C{
}
由于菱形继承,基类被构造了两次。其实,C++也提供了针对菱形继承的解决方案的
//包含A对象
class A{
};
//包含A,B对象
class B:virtual public A{
};
//包含A,C对象
class C:virtual public A{
};
//包含A,B,C,D对象
class D:public B, public C{
}
使用虚继承,B,C对象里面会产生一个指针指向唯一一份A对象。这样付出的代价是必须再运行期根据这个指针的偏移量寻找A对象。
多重继承唯一的那么一点点用就是一个Base class提供public继承,另一个Base class提供private继承。(还是没什么用啊,干嘛不适用复合)
//这里接口要求T必须实现operator >
template<typename T>
T max(T a, T b){
return (a > b) ? a : b;
}
**第一层:**作为类模板的参数时,与 class 功能相同。
template<class T> class MyType;
template<typename T> class MyTYpe;
这两个定义完全相同。
第二层: typename 可以让模板里面定义嵌套从属名称的类型变成有效的类型,因为 C++ 的解析器在模板中遇到嵌套从属类型时,默认认为它是无效的类型。
例如:无效的嵌套从属类型
template<typename T>
void fun(const T& t)
{
T::const_iterator iter(t.begin());
}
我们需要认为指定它为有效的嵌套从属类型
template<typename T>
void fun(const T& t)
{
typename T::const_iterator iter(t.begin());
}
一般情况 当你想在 template 中指定一个有效的嵌套从属类型名称,只需要在嵌套从属类型前面加上 typename 关键字即可。
例外
* 不得在 base class list 中使用 typename
/* 错误用法 */
class Deriver : public typename Base<T>::MyType;
* 不得在 member initialization list 中使用 typename
class Deriver : public Base<T>::MyType
{
public:
/* 错误用法 */
explicit Deriver(int x) : typename Base<T>::MyType(x)
{
}
};
原则
class 和 typename 在声明模板参数时作用相同。
使用 typename 标识嵌套从属类型,但是不得在 base class list 和 member initialization list 中使用。
template <typename T>
class Base{
public:
void print(T a) {cout <<"Base "<< a <<endl;};
};
template<typename T>
class Drive : public Base<T>{
public:
void printf(T a){
//error 编译器不知道基类有print函数
print(a);
}
};
//解决方案
//this->print();
//using Base::print
//base::print直接调用
我们来考虑一下智能指针的拷贝构造函数和赋值操作符怎么实现。它需要子类的智能指针能够隐式转型为父类智能指针
template<typename T>
class shared_ptr{
public:
//拷贝构造函数,接受所有能够从U*隐式转换到T*的参数
template<typename U>
shared_ptr(shared_ptr<U> const &rh):p(rh.get()){
...
}
//赋值操作符,接受所有能够从U*隐式转换到T*的参数
template<typename U>
shared_ptr& operator= (shared_ptr<U> const &rh):p(rh.get()){
...
}
//声明正常的拷贝构造函数
shared_ptr(shared_ptr const &rh);
shared_ptr& operator= (shared_ptr const &rh);
private:
T *p;
}
template <class T>
class Rational
{
…
friend Rational operator* (const Rational& a, const Rational& b)
{
return Rational (a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator());
}
…
}
template<typename T>
class type_traits;
template<>
class type_traits<int>{
public:
static int size = 4;
}
template<>
class type_traits<char>{
public:
static int size = 1;
}
template<>
class type_traits<double>{
static int size = 8;
}
template<typename T>
int ccSizeof(T){
return type_traits<T>::size;
}
本质上就是函数式编程
//上楼梯,每次上一步或者两步,有多少种
int climb(int n){
if(n == 1)
return 1;
if(n == 2)
return 2;
return climb(n - 1) + climb(n - 2);
}
//元编程,采用类模版
template<int N>
class Climb{
public:
const static int n = Climb<N-1>::n + Climb<N-2>::n;
};
template<>
class Climb<2>{
public:
const static int n = 2;
};
template<>
class Climb<1>{
public:
const static int n = 1;
};
当编译器为函数调用生成代码时,首先将参数从右至左压栈,然后是函数返回的地址(Return Address)压栈,同时在函数内部,生成代码来将堆栈指针移动(向上或向下,这要视机器而定),为函数的本地变量提供存储空间。当函数调用完毕,栈指针将移动到函数(Return Address)的位置,这样函数的本地变量出栈。那么函数的返回值(尤其是一个自定义的类型)存放在什么地方?答案是将函数的返回值作为一个参数压栈,直接将返回值的信息拷贝至该参数中。这个答案没有解决所有的问题,但它效率很高。
下面是一个函数调用的例子:
int f(int x,char c);
int g=f(a,b);
看一下它对应的汇编代码:
push b;
push a;
call f();
add sp,4;
mov g,register a;
先是两个参数压栈,然后调用函数,完了将参数出栈,将返回值放在寄存器中(因为int是built-in type),传递给返回值g。这与上面讲的函数调用的过程稍有不同。插一句:前段时间碰到很多次stack overflow的错误,搞死我了。但是当我理解了函数调用背后的故事后,stack overflow的问题终于暂时解决了。
何为copy constructor?
当需要从一个已存在的对象创建另一个对象时,会调用copy constructor。当然,我们也可以阻止这样的行为。忠告中会讲到。
看下面的例子:
class String
{ public:
String(const char *str=NULL);
String(const String &other); //copy constructor
virtual ~String();
String & operator=(const String &other);//assignment operator
private:
char *m_data;
}
如果缺少了copy constructor和assignment operator,当进行复制时会进行bitcopy,也就是按位进行拷贝。试想如果上面的类中没有copy constructor和assignment operator,调用如下语句时的问题:
String a(“hello”);//m_data指向字符串“hello”
String b(“World”);//m_data指向字符串“world”
b=a;
这样,经过bitcopy,a和b中的m_data都指向“hello”,“world”没人管了,Memory Leak!!!而且,当a或b中的一个调用了析构函数后,“hello”所在的内存将被释放,这样另一个中的指针指向了一片非法内存!!!
忠告:如果class的成员变量中含有任何指针,请为这个类写copy constructor和assignment operator。但是你如果确信你的class不会执行copy和assignment动作,这时候写copy constructor和assignment operator会觉得有点得不偿失,这时候将copy-construction(No definition)声明为private,将阻止使用值传递方式(pass an object of your class by value)。呵呵,我知道这个时候你的头在游泳了。
new和malloc对比:
namespace std{
typedef void (*new_handler)();
//返回旧的handler
new_handler set_new_handler(new_handler p) throw();
}
可以为每个类设置专属new handler
C++中对象的构造和析构经历了都两个阶段
替换new和delete的理由,就是需要收集分配内存的资源信息
我们知道,new一个对象要经历两步。如果在调用构造函数失败,编译器会寻找一个“带相同额外参数”的operator delete,否则就不调用,造成资源泄漏
我觉得这个条款讲的不是太通俗,所以我决定来个“俗”点的:
重载new和delete时必须要做到的,这里的重载包括(参见《Thinking in C++》):
Overloading global new & delete;
Overloading new & delete fro a class;
Overloading new & delete for arrays。
不过现在有了这练习上乘内功的口诀,就不怕走火入魔了。
口诀:
解决:
class X{
public:
void f();
static void * operator new(size_t size, new_handler p);//我们重载,为了区别,则多添加一个参数
static void * operator new(size_t size)//这是系统自己的
{ return ::operator new(size);}
}
class X{
public:
void f();
static void * operator new(size_t size, new_handler p=0);
}
class Base{
public:
virtual void print(int a = 1) {cout <<"Base "<< a <<endl;};
int a;
};
class Drive : public Base{
public:
void print(int a = 2){cout << "Drive " << a <<endl;}
};
int main(){
Base *b = new Drive;
b->print(); // vptr[0](1)
}
//Drive 1
我们来考虑一下智能指针的拷贝构造函数和赋值操作符怎么实现。它需要子类的智能指针能够隐式转型为父类智能指针
template<typename T>
class shared_ptr{
public:
//拷贝构造函数,接受所有能够从U*隐式转换到T*的参数
template<typename U>
shared_ptr(shared_ptr<U> const &rh):p(rh.get()){
...
}
//赋值操作符,接受所有能够从U*隐式转换到T*的参数
template<typename U>
shared_ptr& operator= (shared_ptr<U> const &rh):p(rh.get()){
...
}
//声明正常的拷贝构造函数
shared_ptr(shared_ptr const &rh);
shared_ptr& operator= (shared_ptr const &rh);
private:
T *p;
}
本质上就是函数式编程
//上楼梯,每次上一步或者两步,有多少种
int climb(int n){
if(n == 1)
return 1;
if(n == 2)
return 2;
return climb(n - 1) + climb(n - 2);
}
//元编程,采用类模版
template<int N>
class Climb{
public:
const static int n = Climb<N-1>::n + Climb<N-2>::n;
};
template<>
class Climb<2>{
public:
const static int n = 2;
};
template<>
class Climb<1>{
public:
const static int n = 1;
};
C++元编程可以将计算转移到编译期,执行速度迅速(缺陷?)
// 当c是vector、string,删除value
c.erase(remove(c.begin(), c.end(), value), c.end());
// 判断value是否满足某个条件,删除
bool assertFun(valuetype);
c.erase(remove_if(c.begin(), c.end(), assertFun), c.end());
// 有时候我们不得不遍历去完成,并删除
for(auto it = c.begin(); it != c.end(); ){
if(assertFun(*it)){
···
it = c.erase(it);
}
else
++it;
}
c.remove(value);
// 判断value是否满足某个条件,删除
c.remove(assertFun);
* 删除关联容器(set,map)中某个元素
c.erase(value)
for(auto it = c.begin(); it != c.end(); ){
if(assertFun(*it)){
···
c.erase(it++);
}
else
++it;
}