Effective Mordern C++ 学习笔记之——C++11/14的一些新特性

318 阅读3分钟

更多精彩文章,欢迎关注作者微信公众号:码工笔记

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)不抑制特殊函数的生成