[Effective modern cpp]现代c++区别

读书笔记

  • 创建对象时使用()和{}的区别
  • 空指针优先选用nullptr,而非0或null
  • 优先选用别名声明,而非typedef
  • 优先选用限定作用域的枚举类型,而非不限作用域的枚举型别。
  • 优先选用delete删除函数,而非private未定义函数
  • 为所有派生类中有意重写的函数添加override声明
  • 只要函数不会发射异常,就为其加上noexcept声明
  • 只要有可能使用constexpr,就使用它
  • 理解特种成员函数的生成机制

创建对象时使用()和{}的区别

1、大括号可以指定容器的初始内容>。

std::vector<int> v{1,3,5}

2、大括号可以用来为非静态成员指定默认初始化值,也可以用’='的初始化语法,不能用()。

class Widget{
  ...
private:
  int x{0};//可行
  int y = 0;//可行
  int z(0);//不可行
}

3、不可复制对象(只移类型)可以用大括号小括号进行初始化,却不能使用"=":

std::atomic<int> ai1{0};
std::atomic<int> ai2(0);
std::atomic<int> ai3 = 0;//不可行

4、大括号禁止內建型别之间进行隐式窄化型别转换,小括号和=不会进行隐式窄化类型检查。

double x,y,z;
...
int sum1{x+y+z}; //错误!double类型之和可能无法用int表达

5、大括号能够防止解析语法带来的错误。

widget w2();//最令人苦恼之解析语法现身,这个语句声明了一个名为w2、返回一个widget型别对象的函数
widget w3{};//调用没有形参的widget的构造函数

6、大括号的缺陷在于,对于auto类型而言,auto推导结果为std::initializer_list,这样的结果让你出乎意外。另外针对有将std::initializer_list作为构造函数的形参的类而言,当用大括号进行初始化时,会优先匹配std::initializer_list作为形参的构造函数,而不会选择其他有着貌似更加匹配的重载版本。

空指针优先选用nullptr,而非0或null

nullptr的优点在于:它不具备整型型别。
nullptr的实际型别是std::nullptr_t。型别std::nullptr_t可以隐式转换到所有的裸指针型别。在模板推导过程中,0会被推导成整型,null被推导成某种整型。

int f1(std::shared_ptr<Widget> spw);
double f2(std::unique_ptr<Widget> upw);
bool f3(Widget* pw);
std::mutex f1m,f2m,f3m;
using MuxGuard = std::lock_guard<std::mutex>;

template<typename FuncType,
         typename MuxType,
         typename PtrType>
decltype(auto) lockAndCall(FuncType func,MuxType& mutex,PtrType ptr){
	MuxGuard g(mutex);
	return func(ptr);
}

auto result1 = lockAndCall(f1,f1m,0);//错误
auto result2 = lockAndCall(f2,f2m,NULL);//错误
auto result3 = lockAndCall(f3,f3m,nullptr);//没问题

避免在整型和指针型别之间重载。

优先选用别名声明,而非typedef

别名声明可以直接模板化,typedef就不行,需要通过嵌套在模板化的struct里的typedef才能硬搞出来的东西。比如

template<typename T>
using MyAllocList = std::list<T,MyAlloc<T>>;
MyAllocList<Widget> lw;

template<typename T>
struct MyAllocList {
  typedef std::list<T,MyAlloc<T>> type;
}
MyAllocList<Widget>::type lw;

如果你想在模板内使用typedef来创建一个链表,它容纳的对象型别由模板形参制定的话,那你就要给typedef的名字加一个typename前缀:

template<typename T>
class Widget{
private:
  typename MyAllocList<T>::type list;
}
std::remove_const<T>::type;//c++11:const T-T
std::remove_const_t<T>//c++14等价物

std::remove_reference<T>::type;//c++11:T&/T&&-T
std::remove_reference_t<T>//c++14等价物

std::add_lvalue_reference<T>::type;//c++11:T-&T
std::add_lvalue_reference<T>//c++14等价物

优先选用限定作用域的枚举类型,而非不限作用域的枚举型别。

1、 限定作用域的枚举型别仅在枚举型别内可见。它们只能通过强制型别转换至其他型别。
2、不限定作用域的枚举型别在定义枚举的作用域内可见。不限定作用域的枚举型别会发生隐式转换
3、限定作用域的枚举型别和不限范围的枚举型别都支持底层型别制定。限定作用域的枚举型别默认底层型别为int,不限作用域的枚举型别没有默认底层型别(编译器通常会选取足够表示枚举量取值的最小底层型别)。

enum color:std::uint32_t{
  red,
  black,
  blue
};

enum class color:std::uint32_t{
  red,
  black,
  blue
}

4、限定作用域的枚举型别总是可以进行前置声明,而不限范围的枚举型别却只有在指定了默认底层的前提下才可以进行前置声明。

优先选用delete删除函数,而非private未定义函数

1、习惯上删除函数会被声明为public,而非private。(这么做的原因是编译器报错会显示相关函数已经被删除,而如果将删除写在private,则编译器会优先检查函数的访问权限)。
2、任何函数都可以删除,包括非成员函数和模板具现。
3、模板特化是必须在名字空间作用域而非类作用域内撰写。

为所有派生类中有意重写的函数添加override声明

派生类虚函数重写需满足以下要求:
1、基类中的函数必须是虚函数
2、函数名字必须完全相同。
3、函数形参必须完全相同。
4、函数常量性(constness)必须完全相同
5、函数返回值和异常规格必须兼容。
6、函数引用饰词必须完全相同。

只要函数不会发射异常,就为其加上noexcept声明

1、noexcept 声明是函数接口的组成部分(调用方对其可能有依赖)。
2、相对于不带noexcept声明的函数,带有noexcept声明的函数有更多机会得到优化
3、noexcept性质对于移动操作、swap、函数释放函数和析构函数最有价值。
4、大多数函数都是异常中立的、不具备noexcept性质。

只要有可能使用constexpr,就使用它

1、所有constexpr对象都是const对象,而并非所有的const对象都是constexpr对象。(const对象不一定是编译期已知的,而constexpr对象一定是编译期已知的)。

int sz;
const auto arraySize = sz;
std::array<int,arraySize> data; //错误,arraySize的值并非编译期可知

2、constexpr函数如果在调用时传入的编译期常量,则产出编译期常量。如果传入的是直至运行期才知晓的值,则产出运行期值。
3、c++11中,constexpr的函数不得包含多与一个可执行语句。c++14放开的要求,跟正常的函数一致。

理解特种成员函数的生成机制

1、特种成员函数主要包含:默认构造函数、析构函数、复制操作、以及移动操作。
2、移动操作仅当类中未包含用户显示声明的复制操作、移动操作和析构函数时才生成。
3、复制构造函数仅当类中不包含用户显式声明的复制构造函数时才生成,如果该类声明了移动操作则复制构造函数将被删除。复制幅值运算符仅当类中不包含用户显式声明的复制赋值运算符将被删除。在已经存在显式声明的析构函数的条件下,生成复制操作已经成为了被废弃的行为。
4、成员函数模板在任何情况下都不会抑制特种成员函数的生成。

你可能感兴趣的:(C++)