define只是简单的字符串替换没有类型检查。而const是有数据类型的,是要进行判断的,可以避免一些低级错误;
C++里有一个特殊的:类中的静态数据,在类中声明,在类外定义,其中整数可以不定义就使用。
enum可以替换const和define,优雅的实现一个常量的设置。
class A{
private:
enum{NumTurns =5};
int scores[NumTurns]; //constant
public:
int numTurns(){
return NumTurns;
}
};
使用inline函数,替换宏定义函数
const出现在* 左侧,指向的是常量,
const出现在* 右侧,指针本身是常量。
顶层const:指的是const修饰的变量本身是一个常量,星号右边。
底层const:指的是const修饰的变量所指向的对象是一个常量,星号左边。
int a = 10;
int* const b1 = &a; //顶层const,b1本身是一个常量。指针常量
const int* b2 = &a; //底层const,b2本身可变,所指的对象是常量。常量指针
STL中迭代器以指针为模型。
const可以使代码变得更加健壮,确保哪些东西可以修改,哪些东西不可以修改。const还可以定义不同的函数重载。
内置类型如int,string等在默认构造函数进入构造函数体之前就已经完成了初始化,构造函数体中是在进行赋值,一般采用列表初始化的方法,直接进行初始化,直接调用拷贝构造方法,效率更高一些。
class A{
public:
A():a(0){}
private:
int a;
};
C++中不同编译单元中定义的非局部静态对象的初始化顺序是不固定的。所以定义的时候,最好将非局部静态对象定义为局部静态对象。
拷贝构造函数:
派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。
如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全,造成内存泄漏。
如果类不会成为基类,也不要刻意的去写上virtual的析构函数,会引入虚表指针,占据额外的空间。
标准库的string类型不含虚函数,将string作为基类的话,有可能会出现内存泄漏。
想创建一个抽象类,但是没有任何纯虚函数,怎么办?声明纯虚析构函数。
class DBConnection{
public:
static DBConnection create();
void close();
};
class DBConn{
public:
~DBConn(){
// db.close();
// 方案1
try
{
db.close();
}
catch(const std::exception& e)
{
std::abort();
// 或者记录下异常
}
}
private:
DBConnection db;
};
class DBConn
{
public:
void close() //给客户使用的
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed)
{
try //如果客户没关闭,再关闭 双保险
{
db.close();
}
catch (const std::exception &e)
{
std::abort();
// 或者记录下异常
}
}
}
private:
DBConnection db;
bool closed;
};
如果某个操作可能在失败时抛出异常,又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数
析构函数不要抛出异常,因该在内部捕捉异常。如果客户需要对某个操作抛出的异常做出反应,应该将这个操作放到普通函数(而不是析构函数)里面
如果在构造函数中执行virtual函数,派生类对象的基类部分,会首先构造,构造基类时,虚函数永远不会进入派生类。所以要避免这种情况。
该约定主要是为了支持连读和连写,这个操作同样适用于-=,+=,*=等
class A{
public:
A& operator=(const A&rhs){ //返回的是当前类的一个引用。
//..
return *this;
}
A& operator+=(const A&rhs){ //返回的是当前类的一个引用。
//..
return *this;
}
};
主要是要处理 a[i] = a[j] 或者 *px = *py这样的自我赋值。有可能会出现一场安全性问题,或者在使用之前就销毁了原来的对象,例如
class Bitmap{...}
class Widget{
private:
Bitmap *pb;
};
Widget& Widget::operator=(const Widget& rhs){
delete pb; // 当this和rhs是同一个对象的时候,就相当于直接把rhs的bitmap也销毁掉了
pb = new Bitmap(*rhs.pb);
return *this;
}
改进处理:
class Widget{
void swap(Widget& rhs); //交换this和rhs的数据
};
Widget& Widget::operator=(const Widget& rhs){
Widget temp(rhs) //为rhs数据制作一个副本
swap(temp); //将this数据和上述副本数据交换
return *this;
}//出了作用域,原来的副本销毁
为了防止在delete语句执行前return,所以需要用对象来管理这些资源。这样当控制流离开f以后,该对象的析构函数会自动释放那些资源。
在资源管理类里面,如果出现了拷贝复制行为的话,需要注意这个复制具体的含义,从而保证和我们想要的效果一样
下面的代码会对锁进行拷贝,但是并没有达到想要的独立类对象的需求。
class Lock{
public:
explicit Lock(Mutex *pm):mutexPtr(pm){
lock(mutexPtr);//获得资源锁
}
~Lock(){unlock(mutexPtr);}//释放资源锁
private:
Mutex *mutexPtr;
}
Lock m1(&m)//锁定m
Lock m2(m1);
//好像是锁定m1这个锁。。而我们想要的是除了复制资源管理对象以外,还想复制它所包括的资源(deep copy)。通过使用shared_ptr可以有效避免这种情况。
class Lock{
public:
explicit Lock(Mutex* pm):mutexPtr(pm,unlock){ //用互斥量和unlock函数初始化shared_ptr
lock(mutexPtr.get());//作为删除器
}
private:
std::shared_ptr<Mutex> mutexPtr;//获取普通指针
};
主要是为了防止内存泄漏
考虑以下场景:
processWidget(shared_ptr<Widget>(new Widget), priority()) // 可能会造成内存泄漏
内存泄漏的原因为:先执行new Widget,再调用priority, 最后执行shared_ptr构造函数,那么当priority的调用发生异常的时候,new Widget返回的指针就会丢失了。当然不同编译器对上面这个代码的执行顺序不一样。所以安全的做法是:
shared_ptr<Widget> pw(new Widget)
processWidget(pw, priority())
这就是用独立语句将new的对象置入智能指针。凡是有new语句的,尽量放在单独的语句当中,特别是当使用new出来的对象放到智能指针里面的时候
Date(int month, int day, int year);
这一段代码可以有很多问题,例如用户将day和month顺序写反(因为三个参数都是int类型的),可以修改成:
Date(const Month &m, const Day &d, const Year &y);//注意这里将每一个类型的数据单独设计成一个类,同时加上const限定符,为了让接口更加易用,可以对month加以限制,只有12个月份
class Month{
public:
static Month Jan(){return Month(1);}//这里用函数代替对象,主要是方式第四条:non-local static对象的初始化顺序问题
}
对于一些返回指针的问题函数,例如:Investment *createInvestment(); 可以设计成返回类型为智能指针类型:std::shared_ptr<Investment> createInvestment();
有些传递参数的时候用引用和cosnt结合的操作,可以降低很多的构造和析构函数调用。
bool validateStudent(const Student &s);//省了很多构造析构拷贝赋值操作
bool validateStudent(s);
subStudent s;
validateStudent(s);//调用后,则在validateStudent函数内部实际上是一个student类型,如果有重载操作的话会出现问题
对于内置类型和stl标准库中的迭代器和函数对象,pass-by-value更加合适
必须返回对象时,别妄想返回其reference,因为很容易返回一个被删除的局部变量,就算是没被删除,在外部也不好释放堆内存
class WebBrowser{
public:
void clearCache();
void clearHistory();
void removeCookies();
}
member 函数:
class WebBrowser{
public:
......
void clearEverything(){ clearCache(); clearHistory();removeCookies();}
}
non-member non-friend函数:
void clearBrowser(WebBrowser& wb){
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
class Rational{
public:
const Rational operator* (const Rational& rhs)const;
}
Rational oneHalf;
result = oneHalf * 2;
result = 2 * oneHalf;//出错,因为没有int转Rational函数
non-member函数
class Rational{}
const Rational operator*(const Rational& lhs, const Rational& rhs){
return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()* rhs.denominator());
}
result = 2 * oneHalf; //不会出错
member函数中,无法解析2导致编译出错,non-member函数重新定义,就可以对2进行编译。
尽量不要进行强制类型转换:
C++的四种新型类型转换:应优先使用 static_cast。如果需要处理多态性,使用 dynamic_cast。只有在特殊情况下,当其他转换不适用时,才考虑使用 const_cast 或 reinterpret_cast。
它可以在相关类型之间进行转换,例如将浮点数转换为整数,或将派生类的指针转换为基类的指针。
int i = 10;
float f = static_cast<float>(i); // 将int转换为float
主要用于处理多态。
它在运行时执行安全的向下转换(由基类指针转换为派生类指针)。如果转换失败(即如果指针不实际指向派生类对象),则返回 nullptr。Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // 安全的向下转换
用来移除或添加 const(或 volatile)属性。它通常用于指针或引用,允许修改被 const 修饰的变量。
const int ci = 10;
int* modifiable = const_cast<int*>(&ci); // 移除const属性
void* p = new int(10);
int* ip = reinterpret_cast<int*>(p); // 将void*转换为int*
关键字 inline 建议编译器使用函数定义中的代码替换对该函数的每次调用。
理论上,使用内联函数可以加速程序运行,因为它们消除了与函数调用关联的开销。 调用函数需要将返回地址推送到堆栈、将参数推送到堆栈、跳转到函数体,然后在函数完成时执行返回指令。 通过内联函数可以消除此过程。 相对于未内联扩展的函数,编译器还有其他机会来优化内联扩展的函数。
inline 函数的过度使用会让程序的体积变大,内存占用过高
当有代码:
#include"date.h"
#include"address.h"
class Person{
private
Dates m_data;//Dates类
Addresses m_addr;//Addresses类
}
这种代码情况下,Person和Dates以及Addresses文件之间,形成了编译依存关系。如果这些头文件有任何一个改变,或者这些头文件所依赖的其他头文件有所改变,那么含Person class的文件就得重新编译。程序头文件应该有且仅有声明
如何实现解耦?
原代码:
class Person{
private
Dates m_data;
Addresses m_addr;
}
添加一个Person的实现类,定义为PersonImpl,修改后的代码:
class PersonImpl;
class Person{
private:
shared_ptr<PersonImpl> pImpl;
}
全虚函数
class Person{
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
}
模板方法模式:NVI手法:通过public non-virtual成员函数间接调用private virtual函数,即所谓的template method设计模式:这种方法的优点在于事前工作和事后工作,这些工作能够保证virtual函数在真正工作之前之后被单独调用
class GameCharacter{
public:
int healthValue() const{
//做一些事前工作
int retVal = doHealthValue();
//做一些事后工作
return retVal;
}
private:
virtual int doHealthValue() const{
... //缺省算法,计算健康函数
}
}
class GameCharacter; // 前置声明
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);//函数指针
explicit GameCHaracter(HealthCalcFunc hcf = defaultHealthCalc):healthFunc(hcf){}//可以换一个函数的
int healthValue()const{return healthFunc(*this);}
private:
HealthCalcFunc healthFunc;
}
不要重新定义继承而来的non-virtual函数
绝不重新定义继承而来的缺省参数值
:virtual函数是动态绑定,但是缺省参数值是静态绑定。class Shape{
public:
enum ShapeColor {Red, Green, Blue};
virtual void draw(ShapeColor color=Red)const = 0;
};
class Rectangle : public Shape{
public:
virtual void draw(ShapeColor color=Green)const;//和父类的默认参数不同
}
Shape* pr = new Rectangle; // 注意此时pr的静态类型是Shape,但是他的动态类型是Rectangle
pr->draw(); //virtual函数是动态绑定,而缺省参数值是静态绑定,所以会调用Red
template<class T>
class Set{
public:
void insert();
//.......
private:
std::list<T> rep;
}
当derived class 需要访问protect base class 的成员,或者需要重新定义继承而来的virtual函数时
,这么设计是合理的。多重继承:
class BorrowableItem{
public:
void checkOut();
};
class ElectronicGadget{
bool checkOut()const;
};
class MP3Player:public BorrowableItem, public ElectronicGadget{...};
MP3Player mp;
mp.checkOut();//歧义,到底是哪个类的函数
只能使用:
mp.BorrowableItem::checkOut();
class Parent{...};
class First : public Parent(...);
class Second : public Parent{...};
class last:public First, public Second{...};
class Widget {
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
void swap(Widget& other); //第25条
}
void doProcessing(Widget& w){
if(w.size()>10){...}
}
template<typename T>
void doProcessing(T& w)
{
if(w.size()>10){...}
}
// class和typename两者有什么不同? 没有不同
template<class T> class A;
template<typename T> class A;
template<typename C>
void print2nd(const C& container){
if(container.size() >=2)
typename C::const_iterator iter(container.begin());
//这里的typename表示C::const_iterator是一个类型名称,
//因为有可能会出现C这个类型里面没有const_iterator这个类型
//或者C这个类型里面有一个名为const_iterator的变量
}
template class Derived : public typename Base ::Nested{}//错误的!!!!!
class CompanyA{
public:
void sendCleartext(const std::string& msg);
....
};
class CompanyB{....};
class MsgInfo{....};
template <typename Company>
class MsgSender{
public:
void sendClear(const MsgInfo& info){
std::string msg;
Company c;
c.sendCleartext(msg);
}
}
template<typename Company>//想要在发送消息的时候同时写入log,因此有了这个类
class LoggingMsgSender:public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//记录log
sendClear(info);//无法通过编译,因为找不到一个特例化的MsgSender<company>
// 编译的时候,不会主动的调用基类中的函数,因为存在一个特化版本,不存在这个函数会出问题。
}
}
解决方法:对编译器说:base class template的任何特例化版本都支持其一般版本所提供的接口。
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//记录log
this->sendClear(info);//假设sendClear将被继承
}
}
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
using MsgSender<Company>::sendClear; //告诉编译器,请他假设sendClear位于base class里面
void sendClearMsg(const MsgInfo& info){
//记录log
sendClear(info);//假设sendClear将被继承
}
}
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//记录log
MsgSender<Company>::sendClear(info);//假设sendClear将被继承
}
}
template<typename T, std::size_t n>
class SquareMatrix{
public:
void invert(); //求逆矩阵
}
SquareMatrix<double, 5> sm1;
SquareMatrix<double, 10> sm2;
template<typename T>
class SquareMatrixBase{
protected:
void invert(std::size_t matrixSize);
}
template<typename T, std::size_t n>
class SquareMatrix:private SquareMatrixBase<T>{
private:
using SquareMatrixBase<T>::invert; //避免遮掩base版的invert
public:
void invert(){ this->invert(n); } //一个inline调用,调用base class版的invert
}
利用多态性将基类指针指向派生类对象很简单,但是对于包含模板的类型转换是比较麻烦的。因为编译器对于派生类模板参数和基类模板参数,是没有连接关系的。
Top* pt2 = new Bottom; //将Bottom*转换为Top*是很容易的
template<typename T>
class SmartPtr{
public:
explicit SmartPtr(T* realPtr);
};
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);//将SmartPtr<Bottom>转换成SmartPtr<Top>是有些麻烦的
可运用模板成员函数,来进行变动:使用成员函数模板生成“可接受所有兼容类型”的函数
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other) //为了生成copy构造函数
:heldPtr(other.get()){....}
T* get() const { return heldPtr; }
private:
T* heldPtr; //这个SmartPtr持有的内置原始指针
};
进行混合类型算术运算的时候,会出现编译通过不了的情况
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs){....}
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2; //错误,无法通过编译
// no match for 'operator' (operand types are 'Ratinal<int>' and 'int'
解决方法:使用friend声明一个函数,进行混合式调用
这里的friend和非friend函数是没有关联的。类外部的函数,并非为声明函数的实现。
template<typename T>
class Rational{
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs){
return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator() * rhs.denominator());
}
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>&rhs){....}
当我们编写一个class template, 而他所提供的“与此template相关的”函数支持所有参数隐形类型转换时,请将那些函数定义为classtemplate内部的friend函数
Template metaprogramming是编写执行于编译期间的程序
,因为这些代码运行于编译器而不是运行期,所以效率会很高,同时一些运行期容易出现的问题也容易暴露出来
。
template<unsigned n>
struct Factorial{
enum{
value = n * Factorial<n-1>::value
};
};
// 类似递归出口
template<>
struct Factorial<0>{
enum{ value = 1 };
}; //这就是一个计算阶乘的元编程
当new无法申请到新的内存的时候,会不断的调用new-handler,直到找到足够的内存,new_handler是一个错误处理函数:
namespace std{
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
new_handler是一个函数指针,可以用set_new_handler把函数设置到系统中,返回一个new_handler:set_new_handler允许客户制定一个函数,在内存分配无法获得满足时被调用
一个设计良好的new-handler要做下面的事情:
new-handler无法给每个class进行定制,但是可以重写new运算符,设计出自己的new-handler
此时这个new应该类似于下面的实现方式:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc){
NewHandlerHolder h(std::set_new_handler(currentHandler)); // 安装Widget的new-handler
return ::operator new(size);
//分配内存或者抛出异常,恢复global new-handler
}
参考列表:
Effective C++
https://www.bilibili.com/video/BV1Vs4y1z7zo/