更多精彩文章,欢迎关注作者微信公众号:码工笔记
Item 7:构造对象时使用()还是{}
- {}初始化可使用范围最广,用它可防止自动类型转换(narrowing conversion),而且不会受C++的”most vexing parse“机制影响
- narrowing conversion
double x,y,z; ... int sum1{x+y+z}; //error! 此处有double转int的转换,{}初始化不允许- most vexing parse:所有能被解释为声明的语句都被解释成声明,用()的话会将开发者希望的无参构造函数解释为方法声明。
Widget w2(); //此处开发都希望调用的是无参构造函数,但实际编译器会将它看成一个方法声明,从而报错。 Widget w3{}; //使用{}就不会有这个问题 - 编译器在构造函数匹配过程中,如果构造函数中有参数为std::initializer_list的,使用{}初始化会尽可能地匹配std::initializer_list,即使其他构造函数看起来更准确。
- 构造一个 std::vector 并传入两个参数时,使用()和{}意义完全不同
- 在模板类中决定用{}还是()有可能是无最优解的
Item 8:尽量使用nullptr而不是0或NULL
0和NULL都不是指针类型,nullptr是特殊定义的可以指向任何类型的指针。
//有以下三个重载的f方法
void f(int);
void f(bool);
void f(void *);
f(0); //f(int), not f(void *)
f(NULL); //f(int)或编译错误,但不可能调到f(void *)
f(nullptr); //f(void *)
模板类中,nullptr可以用来特化后的指针类型相比较,0或NULL不行。
//模板方法,用来先锁mutex,再调用
template<typename FuncType,
typename MuxType,
typename PtrType>
auto lockAndCall(FuncType func,
MuxType& mutex,
PtrType ptr) -> decltype(func(ptr))
{
using MuxGuard = std::lock_guard<MuxType>;
MuxGuard g(mutex);
return func(ptr);
}
//三个方法,都需要先lock一个mutex再来调用
int f1(std::shared_ptr<Widget> spw);
double f2(std::unique_ptr<Widget> upw);
bool f3(Widget *pw);
std::mutex f1m, f2m, f3m; //mutexes for f1/f2/f3
auto result1 = lockAndCall(f1, f1m, 0); //error!
...
auto reult2 = lockAndCall(f2, f2m, NULL); //error!
...
auto result3 = lockAndCall(f3, f3m, nullptr); //ok
Item 9:尽量使用alias声明而不是typedef
使用起来更清晰:
typedef void (*FP)(int, const std::string&); //typedef
using FP = void (*)(int, const std::string&); //alias声明
支持模板:
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
Item 10:尽量使用scoped enum
enum class Color { black, white, red };
Color c = Color::white;
auto c = Color::white;
if (c < 14.5) { //error! Color和double不能比较
...
}
Item 11: 尽量使用 delete 而不是将其声明为未定义的私有方法
- 调用被声明为 delete 的方法都将报错
- delete不但支持成员方法,也支持普通方法
bool isLucky(int number);
bool isLucky(char) = delete; //禁止使用char
bool isLucky(bool) = delete; //禁止使用bool
- delete也支持模板特化 普通模板函数:
template<typename T>
void processPointer(T* ptr);
template<>
template<>
void processPointer<void>(void*) = delete;
void processPointer<void>(void*) = delete;
模板类方法:
class Widget {
public:
...
template<typename T>
void processPointer(T* ptr)
{...}
...
}
template<>
void Widget::processPointer<void>(void*) = delete; //still public, but deleted
Item 12:将重载的方法用 override 关键字声明
- 子类重载非常重要,但又很容易出错,用override关键字可以帮助查错
class Base {
public:
virtual void mf1() const;
virtual void mf2(int x);
virtual void mf3() &;
void mf4() const;
};
class Derived {
public:
virtual void mf1(); //Base中是const
virtual void mf2(unsigned int x); // int VS. unsigned int
virtual void mf3() &&; //Base中是lvalue-qualified,这里是rvalue-qualified
void mf4() const; //Base中未声明virtual
Item 13:尽量使用 const_iterator
Item 14:如果某方法不抛异常,将其声明为 noexcept
- noexcept是接口的一部分,调用者会依赖它
- noexcept函数比non-noexcept方法的编译优化空间大
- noexcept在move、swap、delete及析构函数的场景下价值较大
- 大部分函数应该是 exception-neutral 而不是 noexcept
Item 15:能用 constexpr 的地方尽量用它
- constexpr 是常量,且被编译期确定的值初始化
- constexpr 函数如果传入的参数也是编译期确定的值,则其返回值也可以编译期得出
- constexpr 也是对象或函数的接口描述的一部分
Item 16:声明为 const 的成员函数应该保证线程安全
Item 17:理解特殊成员函数的生成机制
- 默认构造函数、析构函数、copy、move
- 只有未显式定义 move、copy或destructor的情况编译器才会自动生成 move 函数
- copy constructor只有在未显式声明copy constructor的类中才自动生成,而且如果有声明 move,则copy会被删除
- copy assignment 只有在未声明copy assignment的类中自动生成,而且如果有move,则其也会删除
- 成员函数模板(Member function templates)不抑制特殊函数的生成