【笔记】Effective C++ 设计与声明
【笔记】Effective C++ 设计与声明
条款 18 容易正确而不容易误用的接口
class Date{
public:
Date(int month, int day, int year);
}
- 容易传入错误顺序的参数
- 容易传入无效的值
- 接口的不一致性
- 返回指针可能导致的内存泄露
类型系统(type system) 和 外覆类型 (wrapper types)
- 解决方案:导入外覆类型用于区别类型,限制类型。什么是外覆类型?我的理解是在基础数据类型上包裹上一层结构体,用来区分类型。
struct Day{
explicit Day(int d)
:val(d){}
int val;
};
struct Month{
explicit Month(int d)
:val(d){}
int val;
};
struct Year{
explicit Year(int d)
:val(d){}
int val;
};
class Date{
public:
Date(const Month& month, const Day& day, const Year& year);
};
Date d(Month(10), Day(26), Year(1997));
书中,对于Month, Day, Year使用 class 设计成更加好的类最好。
explicit 关键字:防止隐式转换。
- 解决方案:使用静态变量,限制有限数量的值,类似与单例模式的实现。可以使用
const来限制类型内什么可以做。
class Month{
public:
static Month Jan() { return Month(1);}
static Month Feb() { return Month(2);}
// ...
static Month Dec() { return Month(12);}
private:
explicit Month(int m)
:val(m){}
int val;
}
Date d(Month::May(), Day(30), Year(1997));
-
解决方案:避免与内置类型不兼容 ==> 提供行为一致的接口
-
解决方案:为了不使得客户忘记指针或者忘记使用智能指针,可以直接返回智能指针。(先发制人)
std::tr1::shared_ptr<Class> createClass()
{
std::tr1::shared_ptr<Class> retClass(static_cast<Class>(0), getRidOfClass);
retClass = new Class();
return retClass;
}
shared_ptr 对于每个指针可以绑定一个 删除器, 缺省的删除器来自于当前DLL中。(淦, 我也不是很懂这一句话的意思,Visual Studio实现后报了比较多的错误,先占个坑,后续再继续)
条款 19 设计class犹如设计type
-
新type的对象如何被创建和销毁?
构造函数、析构函数、内存分配函数、释放函数的重载
-
对象初始化和赋值的差别是什么?
-
新type的对象被passed by value,意味着什么?(我也不知道我啊,这一句没理解到位)
-
新type的合法值有哪些?
-
新type需要配合某个继承图系?
-
新type需要什么样的转换?(转换函数怎么写?我懵逼了)
-
什么操作符和函数对新type合理?
-
什么样的标准函数应该驳回?(不清楚)
-
谁应该取用新type成员?(中间的关系有点乱,现在理不清楚)
-
什么是新type的"未声明接口"?(什么是未声明接口?)
-
你的新type有多么一般化?(一个顶一群,听起来挺不错的)
-
真的需要一个新type?(这个问题为什么最后才问?难道是需要思考多点才行?)
这一条款针对class设计方面,感觉懂的很少,感触不深,但应该属于根本重点。
条款 20 宁以pass-by-reference-to-const替换pass-by-value
class Person
{
public:
Person();
virtual ~Person();
private:
std::string name;
std::string address;
}
class Student: public Person()
{
public:
Student();
~Student();
private:
std::string schoolName;
std::string schoolAddress;
}
bool validateStudent(Student s);
Student plato;
bool platoIsOK = validateStudent(plato);
- pass-by-value,validateStudent中会调用默认的Student构造函数,结束时析构一次,4次string的构造,Person的析构和构造。
解决方案:使用 pass-by-reference-to-const
-
避免构造和析构。
-
避免对象分割。
C++编译器底层,reference通过指针实现。内置对象、迭代器、函数对象 pass by value 比 pass by reference快
条款 21 必须返回对象时,莫要返回reference
函数创建对象的内存途径: stack 和 heap
// return by value
const Rational operator*(const Rational& lhs, const Rational& rhs);
// (1) return by reference or pointer, memory in stack
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
return result;
}
// (2) return by reference or pointer, memory in heap
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational* result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
return *result;
}
Rational w, x, y, z;
w = x * y * z;
// (3)return by reference or pointer, memory in static
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
static Rational result;
// ... 对result操作获得结果
return result;
}
bool operator==(const Rational& lhs, const Rational& rhs);
if((a*b) == (c*d))
{
//..
}
(1)在函数中定义临时变量,内存区域在 stack 中。但是当函数被销毁,空间也会被销毁,指向就变成了野指针。
(2)内存分配在 heap 可能造成内存泄露。
(3)内存分配在 static 造成逻辑漏洞,指向同一个空间地址。
解决方案:对象之间,搬移数值的方法最直接就是通过赋值。付出的代价就是一次构造和一次析构
条款 22 将成员变量声明为private
- 从封装角度,只有
private(封装) 和 其他(不封装) - 数据访问的一致性、可细微划分的访问控制、允诺约束条件获得保证、提供class作者充分弹性
这一个条款我理解没有那么深刻,作者在举例public和protected权限的时候,提到的例子也不好理解。
条款 23 宁以 non-member、non-friend 替换 member 函数
- 增加封装性、包裹弹性
- 使用 namespace 来声明 non-member函数
准确来说,我对于namespace理解比较深刻,但是对于包裹弹性这个理解实在没意识。可能没有写到对应的代码。

浙公网安备 33010602011771号