条款16. 成对使用new和delete时要采用相同的形式
1).如果你在表达式中使用[],那必须在相应的delete中使用[]。
2).如果你没在表达式中使用[],那一定不要在相应的delete中使用[]。
typedef std::string MYSTRARR[4]; //定义长度为4的string数组
void test32() {
string* pStr = new string("test");
cout << *pStr << endl;
delete pStr;
string* pStrArr = new string[100]{"Monday","Tuesday","Wednesday","Thursday","Friday"};//
for (int i = 0; i < 5; ++i)
{
cout << pStrArr[i] << endl;
}
delete [] pStrArr;
string* pMsa = new MYSTRARR{"s1111","s2222","s3333","s4444"};//typedef定义的数组new出来后也要用[]释放
for (int i = 0; i < 4; ++i)
{
cout << pMsa[i] << endl;
}
delete[] pMsa;
}
条款17. 以独立语句将newed对象置入智能指针
1).以独立语句将newed对象存储于智能指针内,如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。
int priority(int id,int times) {
return (id - 100) / 10;
}
class Worker {
public:
string m_name;
double m_salary;
};
void ProcessFun(std::tr1::shared_ptr<Worker> pW, int priority)
{
cout << pW->m_name << " priority:" << priority << endl;
}
void test33(){
//ProcessFun(new Worker, priority(1000,3));//无法把Worker* 隐式转换为 std::tr1::shared_ptr<Worker>
//如果new Worker执行后再执行priority(1000, 3)函数,最后执行shared_ptr<Worker>的构造函数
//则在执行priority(1000, 3)函数时如果出现异常会导致内存泄露
ProcessFun(std::tr1::shared_ptr<Worker>(new Worker), priority(1000, 3));//priority(1000, 3)函数如果出现异常可能会导致内存泄露
Worker* pw = new Worker;
ProcessFun(std::tr1::shared_ptr<Worker>(pw), priority(1000, 3));//以独立语句将newed对象存储于智能指针内以避免资源泄露
}
条款18. 让接口容易被正确使用,不易被误用
1).为避免用户输入参数时混淆,可通过定义新类型进行区分,如结构体和类。
struct Day{
explicit Day(int d) :val(d) {}//结构体构造函数,默认public
int val;
};
struct Month {
explicit Month(int m) :val(m) {}
int val;
};
struct Year {
explicit Year(int y) :val(y) {}
int val;
};
class Date {
public:
//为避免直接输入int类型时混淆,可定义名称显而易见的结构体进行区分
Date(Day d, Month m, Year y) :m_day(d), m_month(m), m_year(y) {}
void show() {
cout << m_year.val << "-" << m_month.val << "-" << m_day.val << endl;
}
private:
Day m_day;
Month m_month;
Year m_year;
};
void test34() {
Date date1(Day(20),Month(10),Year(2011));//为避免用户输入参数时混淆,可通过定义新类型进行区分
date1.show();
}
- 为避免用户输入意料之外的无效值,可通过枚举值或者函数限制用户的输入。
2.1)枚举:
void test36() {
enum week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };
week curWeek = Monday;
}
2.2)函数:
class Week {
private:
int m_day;
Week(int day):m_day(day){};
public:
static Week Monday() //为避免用户输入意料之外的无效值,可通过函数限制用户的输入
{
return Week(1);
}
static Week Sunday()
{
return Week(7);
}
};
void test37() {
Week toDay = Week::Monday();
}
3.保持接口的一致性,并尽量与内置类型一致。
4.消除客户的资源删除责任,如通过std::tr1::shared_ptr 自动delete掉new出来的函数,同时也可避免跨dll问题。
class StockInvest {
public:
string m_name;
void invest(string name, int day) {
cout << "invest " << name << endl;
}
};
void destroy(StockInvest* p) {
cout << "destruct StockInvest" << endl;
delete p;
}
std::tr1::shared_ptr<StockInvest> GetStockInvestt() {
StockInvest* pS = new StockInvest();
std::tr1::shared_ptr<StockInvest> ps_ptr(pS, destroy);//指定shared_ptr的删除器
return ps_ptr;
}
void test38() {
{
std::tr1::shared_ptr<StockInvest> si_ptr(GetStockInvestt());
}
int aa = 0;
}
条款19. 设计class犹如设计type
那么如何设计高效的class呢?首先你必须了解你面对的问题。几乎每一个class都面对以下的问题:
- 新type的对象应该如何被创建和销毁?
- 对象的初始化和对象的赋值有什么样的区别?
- 新type的对象如果被passed by value(以值传递),意味着什么?
- 什么是新type的“合法值”?
- 你的新type需要配合某个继承体系吗?
- 你的新type需要什么样的转换?
- 什么样的操作符合函数对此新type而言是合理的?
- 什么样的标准函数应该驳回?
- 谁该采用新type的成员?
- 你的新type有多么一般化?
- 你真的需要一个新的type吗?
条款20. 宁以pass-by-reference-to-const替换pass-by-value
1.用const引用代替值传递避免创建副本的额外开销。(如拷贝构造和析构,若有基类,则还会调用基类的拷贝构造和析构)。
2. 避免传值过程中被对象切割的问题:把派生类对象以值传递的方式传给基类的形参后派生类独有部分会被切割,把派生类对象以引用传递的方式传给基类的形参后派生类独有部分不会被切割。
3.但是对于内置类型,以及STL的迭代器和函数对象,值传递可能更合适
class Man {
public:
Man(string name) :m_name(name) {}
Man(const Man& mp) {
this->m_name = mp.m_name;
}
virtual ~Man() {}
virtual void show() const {
cout << "name:" << m_name << endl;
}
public:
string m_name;
};
class MyChild :public Man {
public:
int m_age;
MyChild(string name, int age) :Man(name), m_age(age) {}
virtual void show() const {
cout << "name:" << m_name << ", age:" << m_age << endl;
}
};
void myCreat(const Man m1) {
m1.show();
}
void myCreatByReference(const Man& m1) {
m1.show();
}
void test42() {
MyChild ch1("zhang",18);
myCreat(ch1);//把派生类对象以值传递的方式传给基类的形参后派生类独有部分会被切割
myCreatByReference(ch1);//把派生类对象以引用传递的方式传给基类的形参后派生类独有部分不会被切割
}
条款21. 必须返回对象时,别妄想返回其reference
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向同一个local static对象而有可能同时需要多个这样的对象。
1.返回局部变量的引用将导致错误。
class Rational {
public:
Rational(int numerator = 0, int denominator = 1):m_numerator(numerator),m_denominator(denominator){};
void show() {
cout << m_numerator << "/" << m_denominator << endl;
}
friend const Rational operator*(const Rational& lhs, const Rational& rhs)//定义乘号运算符,采用值传递是最稳妥的方式
{
Rational res(lhs.m_numerator*rhs.m_numerator, lhs.m_denominator*rhs.m_denominator);
return res;
}
int m_numerator;
int m_denominator;
};
void test43() {
Rational r1(3, 7);
Rational r2(4, 5);
Rational res = r1*r2;
res.show();
}
条款22. 将成员变量变为private
- 让客户访问资源时统一使用函数,保持一致性,可细微划分访问控制,允诺约束条件获得保证。
2.封装内部实现,便于修改。
条款23. 宁以non-member、non-friend替换member函数
- non-member non-friend函数有较大的封装性,因为它并不会增加“能够访问class内的private”的函数数量。
2.可以用namespace的方式将关联性高的函数和类聚合起来。
3.同时使用namespace的方式可将细分功能函数放在不同的头文件中,降低编译依赖性。
条款24.若所有参数皆需类型转换,请为此采用non-member函数
1.如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那个这个函数必须是non-member。
class MyRational {
public:
MyRational(int numerator = 0, int denominator = 1): m_numerator(numerator),m_denominator(denominator){}
int numerator() const { return m_numerator; }
int denominator() const { return m_denominator; }
void show() {
cout << m_numerator << "/" << m_denominator << endl;
}
const MyRational operator*(const MyRational& mr1) {
MyRational tmp(m_numerator*mr1.numerator(),m_denominator*mr1.denominator());
return tmp;
}
private:
int m_numerator;
int m_denominator;
};
//MyRational类的no-member的opetator*
const MyRational operator*(const MyRational& mr1, const MyRational& mr2) {
return MyRational(mr1.numerator()*mr2.numerator(),mr1.denominator()*mr2.denominator());
}
void test47() {
MyRational my1(2, 3);
MyRational my2(4, 7);
MyRational res = my1*my2;
res = res * 2;//构造函数非explicit,自动进行隐式转换
//res = 2*res;//未添加no-member的opetator*时两参数调换位置后无法通过编译
res = 2 * res; //添加no-member的opetator*后可通过编译
res.show();
}
条款25.考虑写出一个不抛异常的swap函数
- 如果默认版本的swap效率不足(通常是你的class或template使用了某种pimpl手法),试着做以下的事情:
1).提供一个public swap成员函数,让它高效的置换两个对象。
2). 在你的 class 或 template 所在的命名空间内提供一个 non-member swap ,并令它调用 上述 swap 成员函数。
3). 如果你正在编写一个 class (而非class template),为你的 class 特化 std::swap。并令它调用你的 swap成员函数。
4).最后,如果你调用 swap ,请确定包含一个 using 声明式,以便让 std::swap 在你的函数内曝光可见,然后不加任何 namespace 修饰符,赤裸裸地调用 swap。
2.实例:
1),写出一个针对widget的std::swap特化版本:先定义一个swap的成员函数,然后在std::swap中调用该成员函数
class WidgetImpl {
public:
WidgetImpl(int a, int b, int c, vector<double>& v) :m_a(a), m_b(b), m_c(c),m_v(v) {}
private:
int m_a, m_b, m_c;
vector<double> m_v;
};
class Widget {
public:
Widget(WidgetImpl* pImpl) :m_pImpl(pImpl) {}
void swap(Widget& tmp) //先定义一个swap的成员函数
{
using std::swap;
swap(m_pImpl, tmp.m_pImpl);
}
private:
WidgetImpl* m_pImpl;
};
namespace std {
template<> void swap(Widget& a, Widget& b) //针对widget的std::swap特化版本
{
a.swap(b);// 在std::swap中调用该成员函数
}
}
void test50() {
WidgetImpl wi1(1, 2, 3, vector<double>{1, 2, 3, 4, 5});
WidgetImpl wi2(11, 12, 13, vector<double>{11, 12, 13, 14, 1});
swap(wi1,wi2);
}
2). 可重载模板函数,但标准std空间不支持重载,可能导致意外问题
template<typename T> class MyWidgetImpl {
public:
MyWidgetImpl(T a, T b, T c, vector<double>& v) :m_a(a), m_b(b), m_c(c), m_v(v) {}
private:
T m_a, m_b, m_c;
vector<double> m_v;
};
template<typename T> class MyWidget {
public:
MyWidget(T* pImpl) :m_pImpl(pImpl) {}
void swap(MyWidget<T>& tmp) {
using std::swap;
swap(m_pImpl, tmp.m_pImpl);
}
private:
T* m_pImpl;
};
/*
namespace std {
template<typename T> void swap<Widget<T>>(MyWidget<T>& a, MyWidget<T>& b) //对swap函数偏特化无法通过编译
{
a.swap(b);
}
}
*/
namespace std {
template<typename T> void swap(MyWidget<T>& a, MyWidget<T>& b) //可重载模板函数,但标准std空间不支持重载,可能导致意外问题
{
a.swap(b);
}
}
void test51() {
MyWidgetImpl<int> mwi1(1, 2, 3, vector<double>{1, 2, 3, 4, 5});
MyWidgetImpl<int> mwi2(11, 12, 13, vector<double>{11, 12, 13, 14, 1});
MyWidget<MyWidgetImpl<int>> mw1(&mwi1);
MyWidget<MyWidgetImpl<int>> mw2(&mwi2);
swap(mw1, mw2);
}
3).在类所在的名称空间内添加non-member版本的swap:
namespace WidgetStuff {
template<typename T> class MyWidgetImpl {
public:
MyWidgetImpl(T a, T b, T c,vector<double>& v) :m_a(a), m_b(b), m_c(c),m_v(v) {}
private:
T m_a, m_b, m_c;
vector<double> m_v;
};
template<typename T> class MyWidget {
public:
MyWidget(T* pImpl) :m_pImpl(pImpl) {}
void swap(MyWidget<T> & tmp) {
using std::swap;
swap(tmp.m_pImpl, m_pImpl);
}
private:
T* m_pImpl;
};
template<typename T> void swap(MyWidget<T>& a, MyWidget<T>& b) {
a.swap(b);
}
}