effective_modern_c++
  • 前言
  • 1 类型推导
    • 条款1 理解模板类型推导
    • 条款2 理解auto类型推导
    • 条款3 理解decltype
    • 条款4 知道如何查看类型推导
  • 2 auto
    • 条款5 优先使用auto而非显式声明
    • 条款6 当auto推导出非预期类型时应当使用显式的类型初始化
  • 3 使用高级cpp特性
    • 条款7 创造对象时区分()和{}
    • 条款8 优先使用nullptr而不是0或者NULL
    • 条款9 优先使用声明别名而不是typedef
    • 条款10 优先使用作用域限制的enum而不是无作用域的enum
    • 条款11 优先使用delete关键字删除函数而不是private却又不实现的函数
    • 条款12 使用override关键字声明覆盖的函数
    • 条款13 优先使用const_iterator而不是iterator
    • 条款14 如果函数不抛出异常请使用noexcept
    • 条款15 尽可能使用constexpr
    • 条款16 让const成员函数线程安全
    • 条款17 理解特殊成员函数的生成
  • 4 智能指针
    • 条款18 对于独占资源使用std::unique_ptr
    • 条款19 对于共享资源使用std::shared_ptr
    • 条款20 当std::shared_ptr可能悬空时使用std::weak_ptr
    • 条款21 优先考虑使用std::make_unique和std::make_shared,而非直接使用new
    • 条款22 当使用Pimpl惯用法,请在实现文件中定义特殊成员函数
  • 5 右值引用和完美转发
    • 条款23 理解std::move和std::forward
    • 条款24 区分通用引用和右值引用
    • 条款25 对右值引用使用std::move,对通用引用使用std::forward
    • 条款26 避免在通用引用上重载
    • 条款27 熟悉通用引用重载的替代方法
    • 条款28 理解引用折叠
    • 条款29 假定移动操作不存在,成本高,未被使用
    • 条款30 熟悉完美转发失败的情况
  • 6 lambda表达式
    • 条款31 避免使用默认捕获模式
    • 条款32 使用初始化捕获来移动对象到闭包中
    • 条款33 对auto&&形参使用decltype以便std::forward它们
    • 条款34 考虑lambda而非std::bind
  • 7 并发api
    • 条款35 优先考虑基于任务的编程而非基于线程的编程
    • 条款36 如果有异步的必要请指定std::async::launch
    • 条款37 使std::thread在所有路径最后都不可结合
    • 条款38 关注不同线程句柄的析构行为
    • 条款39 对于一次性事件通信考虑使用void的futures
    • 条款40 对于并发使用std::atomic,对于特殊内存使用volatile
  • 8 些许调整
    • 条款41 对于移动成本低且总是被拷贝的可拷贝形参,考虑按值传递
    • 条款42 考虑使用置入代替插入
Powered by GitBook
On this page
  1. 3 使用高级cpp特性

条款9 优先使用声明别名而不是typedef

条款18可以说服你使用std::unique_ptr也是个好想法,但是我想绝对我们中间没有人喜欢写像这样std::unique_ptr<std::unordered_map<std::string, std::string>>的代码多于一次。

为了避免这样的情况,推荐使用一个typedef:

typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;

但是typedef家族是有如此浓厚的C++98气息。它们的确可以在C++11下工作,但是C++11也提供了声明别名(alias declaration):

using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;

考虑到typedef和声明别名具有完全一样的意义,推荐其中一个而排斥另外一个的坚实技术原因是容易令人生疑的。这样的质疑也是合理的。

技术原因当然存在,但是在我提到之前,我想说的是,很多人发现使用声明别名可以使涉及到函数指针的类型的声明变的容易理解:

// FP等价于一个函数指针,这个函数的参数是一个int类型和std::string常量类型,没有返回值
typedef void (*FP)(int, const std::string&);  // typedef

// 同上
using FP = void (*)(int, const std::string&);  // 声明别名

当然,上面任何形式都不是特别让人容易下咽,并且很少有人会花费大量的时间在一个函数指针类型的标识符上,所以这很难当做选择声明别名而不是typedef的不可抗拒的原因。

但是,一个不可抗拒的原因是真实存在的:模板。尤其是声明别名有可能是模板化的(这种情况下,它们被称为模板别名(alias template)),然而typedef这里只能说句“臣妾做不到”。模板别名给C++11程序员提供了一个明确的机制来表达在C++98中需要黑客式的将typedef嵌入在模板化的struct中才能完成的东西。举个栗子,给一个使用个性化的分配器MyAlloc的链接表定义一个标识符。使用别名模板,这就是小菜一碟:

template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;  // MyAllocList<T>等同于std::list<T, MyAllocM<T>>

MyAllocList<Widget> lw;  // 客户端代码

使用typedef,你不得不从草稿图开始去做一个蛋糕:

template<typename T>
struct MyAllocList {
    typedef std::list<T, MyAlloc<T>> type;
};  // MyAllocList<T>::type等同于std::list<T, MyAllocM<T>>

MyAllocList<Widget>::type lw;  // 客户端代码

如果你想在一个模板中使用typedef来完成一个节点类型可以被模板参数指定的链接表的任务,你必须在typedef名称使用之前使用typename:

template<typename T>  // Widget<T>包含一个MyAllocList<T>作为一个数据成员
class Widget {
private:
    typename MyAllocList<T>::type list;
    ...
};

此处,MyAllocList<T>::type表示一个依赖于模板类型参数T的类型,因此MyAllocList<T>::type是一个依赖类型(dependent type),C++中许多令人喜爱的原则中的一个就是在依赖类型的名称之前必须冠以typename。

如果MyAllocList被定义为一个声明别名,就不需要使用typename(就像笨重的::type后缀):

template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;  // 和之前一样

template<typename T>
class Widget {
private:
    MyAllocList<T> list;  // 没有typename,没有::type
    ...
};

对你来说,MyAllocList<T>(使用模板别名)看上去依赖于模板参数T,正如MyAllocList<T>::type(使用内嵌的typedef)一样,但是你不是编译器。当编译器处理Widget遇到MyAllocList<T>(使用模板别名),编译器知道MyAllocList<T>是一个类型名称,因为MyAllocList是一个模板别名:它必须是一个类型。MyAllocList<T>因此是一个非依赖类型(non-dependent type),标识符typename是不需要和不允许的。

另一方面,当编译器在Widget模板中遇到MyAllocList<T>(使用内嵌的typename)时,编译器并不知道它是一个类型名,因为有可能存在一个特殊化的MyAllocList,只是编译器还没有扫描到,在这个特殊化的MyAllocList中MyAllocList<T>::type表示的并不是一个类型。这听上去挺疯狂的,但是不要因为这种可能性而怪罪于编译器。是人类有可能会写出这样的代码。

例如,一些被误导的人们可能会杂糅出像这样的代码:

class Wine {...};

template<>
class MyAllocList<Wine> {  // 当T是Wine时,MyAllocList是特殊化的
private:
    enum class WineType {White, Red, Rose};

    WineType type;  // 在这个类中,type是个数据成员
    ...
};

正如你看到的,MyAllocList<Wine>::type并不是指一个类型。如果Widget被使用Wine初始化,Widget模板中的MyAllocList<T>::type指的是一个数据成员,而不是一个类型。在Widget模板中,MyAllocList<T>::type是否指的是一个类型忠实的依赖于传入的T是什么,这也是编译器坚持要求你在类型前面冠以typename的原因。

归纳

  • typename不支持模板化,但是别名声明支持

  • 模板别名避免了::type后缀,在模板中,typedef还经常要求使用typename前缀

Previous条款8 优先使用nullptr而不是0或者NULLNext条款10 优先使用作用域限制的enum而不是无作用域的enum

Last updated 2 years ago