Data::Data(int month, int day, int year) { ... }
三个参数类型相同的函数容易造成误用
Date d1(29, 5, 2014); //调用顺序错乱,应该是 5, 29, 2014
Date d2(2, 30, 2014); //传入参数有误,2月没有30号
导入新的类型
struct Month {
explicit Month(int mValue):Value(mValue){}
int Value;
};
struct Day {
explicit Day(int mValue):Value(mValue){}
int Value;
};
struct Year {
explicit Year(int mValue):Value(mValue){}
int Value;
};
Date d2(2, 30, 2014); //error,类型错误
Date d3(Day(30), Month(2), Year(2014)); //error,类型错误
Date d4(Month(2), Day(30), Year(2014)); // 正确
限制取值
struct Month
{
enum E_MON{JAN = 1, FEC, MAR, APR, MAY, JUN, JUL, AGU, SEP, OCT, NOV, DEC};
explicit Month(const E_MON month) : m_month(month) {}
private:
int m_month;
};
std::shared_ptr<Investment> createInvestment(){
std::shared_ptr<Investment> retVal(static_cast<Invertment*>(0), getRidofInvestment)
//因为0不是指针,需要强转
//getRidofInvestment函数作为删除器
retVal = ...; //令retVal指向正确对象
return retVal;
}
如果被pInv管理的原始指针可以在建立pInv之前确定下来,将其传给pInv的构造函数会比,先将pInv初始化为null再赋值为佳,原因条款26
c++函数默认传值
函数接口应该以const引用的形式传参,而不应该是按值传参。
传值涉及大量参数的复制,这些副本大多是没有必要的
按引用传参也可以避免对象切片(Object slicing) 的问题 (对于多态而言,将父类设计成按值传参,如果传入的是子类对象,仅会对子类对象的父类部分进行拷贝,即部分拷贝,而所有属于子类的特性将被丢弃,造成不可预知的错误,同时虚函数也不会被调用)
小的类型并不意味着按值传参的成本就会小。类型大小与编译器的类型和版本有很大关系,某些类型在特定编译器上编译结果会比其他编译器大得多。小的类型也无法保证在日后始终很小。
只有内置类型,以及STL的选代器和函数对象,传值更合适
绝不要返回pointer或reference指向一个local stack对象,局部变量在函数结束时就销毁了
或返回reference指向一个heap-allocated对象,额外控制delete可能出错
w = x * y * z; //重载了operator*,如果引用指向的是堆内存,会内存泄漏
或返回 pointer或 reference指向一个local static对象而有可能同时需要多个这样的对象
对于C++11以上的编译器,可以采用给类型编写“转移构造函数”以及使用std::move()函数更加优雅地消除由于拷贝造成的时间和空间的浪费。
条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。(单例模式)
请对class内所有成员变量声明为private,private意味着对变量的封装。
从语法一致性看,所有的变量都是private,那么所有的public和protected成员都是函数了,用户在使用的时候也就无需区分
所有类的使用者想利用私有变量实现自己的业务功能时,就必须通过我们留出的接口,这样的接口便充当了一层缓冲,将类型内部的升级和改动尽可能的对客户不可见——不可见就是不会产生影响
protected 并不比public更具封装性
public、protected和private三者反应的是类设计者对类成员封装特性的不同思路——对成员封装还是不封装,如果不封装是对第一类客户不封装还是对第二类客户不封装。
class WebBrowser { // 一个浏览器类
public:
void clearCache(); // 清理缓存,直接接触私有成员
void clearHistory(); // 清理历史记录,直接接触私有成员
void clearCookies(); // 清理cookies,直接接触私有成员
void clearBrowser(); // 在内部调用上边三个函数,不直接接触私有成员,应该放在类外
}
无需直接访问private成员,而只是若干public函数集成而来的member函数。本条款告诉我们:这些函数应该尽可能放到类外。
关于类的封装性:封装的作用是尽可能减小被封装成员的改变对类外代码的影响。某成员封装的好坏:看类内有多少(public或protected)函数直接访问到了这个成员,这样的函数越多,该成员的封装性就越差——该成员的改动对类外代码的影响就可能越大。对于上述clearBrowser函数,设计者本意是不应直接访问任何私有成员,只是公有成员的简单集成(没有必要让其也拥有访问类中private成员的能力),以最大程度维护封装性。但在类的未来维护中,可能忘记设定,在此函数中添加对私有成员的直接访问
成员函数不仅可以访问private成员变量,也可以取用private函数、enums、typedefs等等。而非成员非友元函数能实现更大的封装性,因为它只能访问public函数
关于包括弹性和机能扩展性:提取至类外,通过不同的工具类或者namespace来明确责任,可以从更多维度组织代码结构,并优化编译依赖关系。当我们使用不同功能时就可以include不同的头文件,而不用在面对cache的需求时不可避免的将cookies的工具函数包含进来,降低编译依存性。这也是namespace可以跨文件带来的好处。
命名空间可以跨越多个源码文件而类则不可以。
//在C++中,比较自然的做法是让clearBrowser()函数成为一个non-member函数并且位于WebBrowser类所在的同一个命名空间(namespace)中。
namespace WebBrowserStuff {
class WebBrowser { //核心机能
public :
void clearCache();
void clearHistory();
void clearCookies();
};
// non-member函数,提供几乎所有客户都需要的核心机能
void clearBrowser(WebBrowser& wb) {
wb.clearCache();
wb.clearHistory();
wb.clearCookies();
}
}
一个像WebBrowser这样的类中可能有大量的便利函数,如书签便利函数、打印便利函数、cookies管理有关的便利函数.为了防止多个便利函数之间发生编译相互依赖性,分离它们的最直接方法是将书签便利函数声明在一个头文件中,将cookies管理有关的便利函数声明在另一个头文件中,再将打印便利函数声明于第三个头文件中
// 头文件webbrowser.h,这个头文件针对WebBrowser类
namespace WebBrowserStuff{
class WebBrowser{
// ...
};
// ... non-member函数
}
// 头文件webbrowserbookmarks.h
namespace WebBrowserStuff{
// ... 与书签相关的便利函数
}
// 头文件webbrowsercookies.h
namespace WebBrowserStuff{
// ... 与cookies管理相关的便利函数
}
本条款讨论的是那些不直接接触私有成员的函数,如果你的public(或protected)函数必须直接访问私有成员,那请忘掉这个条款
这个条款告诉了我们操作符重载被重载为成员函数和非成员函数的区别
如果一个操作符是成员函数,那么它的第一个操作数(即调用对象)不会发生隐式类型转换。
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数,第一个操作数,即调用对象)进行类型转换,那么这个函数必须是个non-member。
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
};
Rational oneHalf(1, 2);
result = oneHalf * 2; // 正确
result = 2 * oneHalf; // 报错
等价于
result = oneHalf.operator*(2); // 正确
result = 2.operator*(oneHalf); // 报错
应放在类外
const Rational operator*(const Rational& lhs, Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}
std::swap函数在 C++11 后改为了用std::move实现,因此几乎已经没有性能的缺陷
原文的思想:
函数匹配优先级:普通函数 > 特化函数 > 模板函数