Effective Modern C++:42条改善C++11和C++14代码的建议
深入理解 Effective Modern C++ 的核心思想,掌握现代C++最佳实践
引言
《Effective Modern C++》是 Scott Meyers 的经典著作,提供了 42 条改善 C++11 和 C++14 代码的具体建议。本文总结这些核心要点,帮助你写出更现代、更高效的 C++ 代码。
核心主题 :
类型推导的陷阱与最佳实践
auto 的正确使用
移动语义和完美转发
智能指针的选择
Lambda 表达式的优化
并发编程
第一章:类型推导
Item 1: 理解模板类型推导
模板类型推导有三种情况,取决于 ParamType 的形式。
规则总结 :
graph TD
A[模板推导] --> B{ParamType类型}
B -->|指针/引用| C[保留const]
B -->|万能引用| D[左值/右值分别推导]
B -->|按值传递| E[丢弃const和引用]
关键要点 :
引用和指针不同时,const 被保留
万能引用(T&&)区分左值和右值
按值传递会忽略 const 和引用
Item 2: 理解 auto 类型推导
auto 与模板推导几乎完全相同,唯一例外是花括号初始化。
1 2 3 4 auto x1 = 27 ; auto x2 (27 ) ; auto x3 = {27 }; auto x4{27 };
要记住的事 :
auto 推导通常与模板推导相同
auto 假定花括号初始化代表 std::initializer_list
函数返回值或 lambda 参数中的 auto 使用模板推导规则
Item 3: 理解 decltype
decltype 总是返回表达式的确切类型,不会丢失 const 或引用。
1 2 3 4 5 6 7 8 9 const int i = 0 ;auto a = i; decltype (i) d = i; template <typename Container, typename Index>decltype (auto ) authAndAccess (Container&& c, Index i) { return std::forward<Container>(c)[i]; }
括号陷阱 :
1 2 3 int x = 10 ;decltype (x) t1; decltype ((x)) t2;
Item 4: 学会查看类型推导结果
三种方法:
IDE 编辑器 :快速但可能不准
编译器诊断 :利用编译错误
运行时输出 :typeid 和 Boost.TypeIndex
1 2 3 4 5 template <typename T>class TD ; TD<decltype (x)> xType;
第二章:auto
Item 5: 优先使用 auto 而非显式类型声明
优点 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int x; auto x = 0 ; std::unordered_map<std::string, int > m; for (const std::pair<std::string, int >& p : m) { }for (const auto & p : m) { }std::function<bool (const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> func; auto func = [](const auto & lhs, const auto & rhs) { return *lhs < *rhs; };
性能优势 :
场景
显式类型
auto
性能
Lambda
std::function
auto
auto 快 2-10倍
容器遍历
可能类型错误
总是正确
避免拷贝
闭包
无法表达
完美捕获
零开销
Item 6: 当 auto 推导出非预期类型时使用显式类型初始化
代理类陷阱 :
1 2 3 4 5 6 std::vector<bool > features (const Widget& w) ;auto highPriority = features (w)[5 ]; bool highPriority = features (w)[5 ];
解决方案 :显式类型转换
1 auto highPriority = static_cast <bool >(features (w)[5 ]);
其他代理类场景 :
std::vector<bool>::reference
Matrix 表达式模板
智能指针的代理
第三章:转向现代C++
Item 7: 创建对象时区分 () 和 {}
三种初始化语法 :
1 2 3 int x (0 ) ; int y = 0 ; int z{0 };
花括号优点 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Widget { int x{0 }; int y = 0 ; int z (0 ) ; }; double x = 3.14 ;int y{x}; int z (x) ; Widget w1 (10 ) ; Widget w2 () ; Widget w3{};
陷阱 :std::initializer_list 构造函数
1 2 std::vector<int > v1 (10 , 20 ) ; std::vector<int > v2{10 , 20 };
Item 8: 优先使用 nullptr 而非 0 或 NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void f (int ) ;void f (bool ) ;void f (void *) ;f (0 ); f (NULL ); f (nullptr ); template <typename FuncType, typename PtrType>decltype (auto ) call (FuncType func, PtrType ptr) { return func (ptr); } auto result1 = call (f, 0 ); auto result2 = call (f, nullptr );
Item 9: 优先使用别名声明而非 typedef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS; using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;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;
Item 10-11: 优先使用限域 enum 和 deleted 函数
限域枚举 :
1 2 3 4 5 6 7 8 9 enum Color { black, white, red };auto white = false ; enum class Color { black, white, red };auto white = false ; Color c = Color::white; auto c = Color::white;
deleted 函数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool isLucky (int number) ;bool isLucky (char ) = delete ; bool isLucky (bool ) = delete ; bool isLucky (double ) = delete ; template <typename T>void processPointer (T* ptr) ;template <>void processPointer <void >(void *) = delete ;template <>void processPointer <char >(char *) = delete ;
Item 12-15: 特殊成员函数和优化
noexcept 的重要性 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Widget {public : Widget (Widget&& rhs) noexcept : name (std::move (rhs.name)) {} Widget& operator =(Widget&& rhs) noexcept { name = std::move (rhs.name); return *this ; } private : std::string name; }; std::vector<Widget> vw; vw.push_back (Widget ());
第四章:智能指针
Item 18: 使用 std::unique_ptr 管理独占所有权资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Investment { };class Stock : public Investment { };auto makeInvestment () { return std::make_unique <Stock>(); } auto delInvmt = [](Investment* pInvestment) { makeLogEntry (pInvestment); delete pInvestment; }; template <typename ... Ts>std::unique_ptr<Investment, decltype (delInvmt) > makeInvestment (Ts&&... params) { std::unique_ptr<Investment, decltype (delInvmt) > pInv (nullptr , delInvmt) ; if () pInv.reset (new Stock (std::forward<Ts>(params)...)); return pInv; }
特点 :
零开销(与裸指针相同大小)
独占所有权
可转换为 shared_ptr
Item 19: 使用 std::shared_ptr 管理共享所有权资源
1 2 3 4 5 6 7 8 9 10 auto loggingDel = [](Widget *pw) { makeLogEntry (pw); delete pw; }; std::unique_ptr<Widget, decltype (loggingDel) > upw (new Widget, loggingDel) ; std::shared_ptr<Widget> spw (new Widget, loggingDel) ;
引用计数机制 :
graph LR
A[shared_ptr 1] --> C[控制块]
B[shared_ptr 2] --> C
C --> D[对象]
C --> E[引用计数: 2]
C --> F[弱引用计数]
C --> G[删除器]
性能开销 :
控制块动态分配
引用计数原子操作
虚函数调用(删除器)
Item 20: 使用 std::weak_ptr 解决悬空指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 auto spw = std::make_shared <Widget>();std::weak_ptr<Widget> wpw (spw) ; spw = nullptr ; if (spw == nullptr ) { } if (wpw.expired ()) { } std::shared_ptr<Widget> spw2 = wpw.lock (); auto spw3 = wpw.lock ();
应用场景 :
缓存 :
1 2 3 4 5 6 7 8 9 10 11 std::shared_ptr<const Widget> fastLoadWidget (WidgetID id) { static std::unordered_map<WidgetID, std::weak_ptr<const Widget>> cache; auto objPtr = cache[id].lock (); if (!objPtr) { objPtr = loadWidget (id); cache[id] = objPtr; } return objPtr; }
观察者模式 :
1 2 3 4 5 6 7 8 9 10 11 class Subject { std::vector<std::weak_ptr<Observer>> observers; public : void notify () { for (auto & wo : observers) { if (auto o = wo.lock ()) { o->update (); } } } };
Item 21: 优先使用 std::make_unique 和 std::make_shared
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 std::shared_ptr<Widget> spw (new Widget) ;auto spw = std::make_shared <Widget>();processWidget (std::shared_ptr <Widget>(new Widget), computePriority ());processWidget (std::make_shared <Widget>(), computePriority ());auto spw1 = std::shared_ptr <Widget>(new Widget); auto spw2 = std::make_shared <Widget>();
不能使用 make 函数的情况 :
1 2 3 4 5 6 7 8 9 auto deleter = [](Widget* pw) { delete pw; };std::unique_ptr<Widget decltype (deleter) > upw (new Widget, deleter) ;auto spv = std::make_shared<std::vector<int >>(10 , 20 ); auto initList = {10 , 20 };auto spv = std::make_shared<std::vector<int >>(initList);
第五章:右值引用、移动语义和完美转发
Item 23: 理解 std::move 和 std::forward
std::move 无条件转换为右值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Widget {public : Widget (Widget&& rhs) : name (std::move (rhs.name)), p (std::move (rhs.p)) {} private : std::string name; std::shared_ptr<int > p; }; template <typename T>decltype (auto ) move (T&& param) { using ReturnType = remove_reference_t <T>&&; return static_cast <ReturnType>(param); }
std::forward 条件转换 :
1 2 3 4 5 6 7 8 9 void process (const Widget& lvalArg) ; void process (Widget&& rvalArg) ; template <typename T>void logAndProcess (T&& param) { auto now = std::chrono::system_clock::now (); makeLogEntry ("Calling 'process'" , now); process (std::forward<T>(param)); }
对比 :
特性
std::move
std::forward
用途
无条件转右值
条件转发
参数
通用引用
通用引用
使用场景
移动构造/赋值
完美转发
Item 24: 区分万能引用和右值引用
万能引用的判断标准 :
1 2 3 4 5 6 7 8 9 template <typename T>void f (T&& param) ; auto && var2 = var1; void f (Widget&& param) ; template <typename T>void f (std::vector<T>&& param) ;
Item 25: 对右值引用使用 std::move,对万能引用使用 std::forward
1 2 3 4 5 6 7 8 9 10 11 12 13 class Widget {public : Widget (Widget&& rhs) : name (std::move (rhs.name)) {} template <typename T> void setName (T&& newName) { name = std::forward<T>(newName); } private : std::string name; };
错误示例 :
1 2 3 4 5 6 7 8 9 Widget (Widget&& rhs) : name (std::forward<Widget>(rhs).name) {} template <typename T>void setName (T&& newName) { name = std::move (newName); }
Item 26: 避免重载万能引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 std::multiset<std::string> names; template <typename T>void logAndAdd (T&& name) { auto now = std::chrono::system_clock::now (); log (now, "logAndAdd" ); names.emplace (std::forward<T>(name)); } logAndAdd (std::string ("Persephone" )); logAndAdd ("Patty Dog" ); std::string petName ("Darla" ) ;logAndAdd (petName); short nameIdx = 22 ;logAndAdd (nameIdx);
Item 27-30: 熟悉完美转发失败的情况
完美转发失败的情况 :
花括号初始化 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void f (const std::vector<int >& v) ;f ({1 , 2 , 3 }); template <typename T>void fwd (T&& param) { f (std::forward<T>(param)); } fwd ({1 , 2 , 3 }); auto il = {1 , 2 , 3 };fwd (il);
0 或 NULL 作为空指针 :
1 2 3 fwd (NULL ); fwd (0 ); fwd (nullptr );
仅声明的 static const 成员变量 :
1 2 3 4 5 6 7 8 9 class Widget {public : static const std::size_t MinVals = 28 ; }; std::vector<int > widgetData; widgetData.reserve (Widget::MinVals); fwd (Widget::MinVals);
重载函数名和模板名
位域
第六章:Lambda表达式
Item 31: 避免默认捕获模式
按值捕获的问题 :
1 2 3 4 5 6 7 8 9 10 11 12 using FilterContainer = std::vector<std::function<bool (int )>>;FilterContainer filters; void addDivisorFilter () { auto calc1 = computeSomeValue1 (); auto calc2 = computeSomeValue2 (); auto divisor = computeDivisor (calc1, calc2); filters.emplace_back ( [=](int value) { return value % divisor == 0 ; } ); }
指针捕获的问题 :
1 2 3 4 5 6 7 8 9 10 11 class Widget {public : void addFilter () const { filters.emplace_back ( [=](int value) { return value % divisor == 0 ; } ); } private : int divisor; };
正确做法 :
1 2 3 4 5 6 7 8 9 10 11 12 class Widget {public : void addFilter () const { auto divisorCopy = divisor; filters.emplace_back ( [divisorCopy](int value) { return value % divisorCopy == 0 ; } ); } };
Item 32: 使用初始化捕获将对象移入闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 auto pw = std::make_unique <Widget>();auto func = [pw = std::move (pw)] { return pw->isValidated () && pw->isArchived (); }; auto func = std::bind ( [](const std::unique_ptr<Widget>& pw) { return pw->isValidated () && pw->isArchived (); }, std::make_unique <Widget>() );
Item 33-34: Lambda 与 std::function
优先使用 auto 而非 std::function :
1 2 3 4 5 std::function<bool (int )> func1 = [](int x) { return x > 0 ; }; auto func2 = [](int x) { return x > 0 ; };
性能对比 :
特性
std::function
auto
内存
固定大小,可能堆分配
闭包大小
内联
几乎不可能
容易内联
性能
慢
快
第七章:并发API
Item 35: 优先使用基于任务而非基于线程的编程
1 2 3 4 5 6 7 int doAsyncWork () ;std::thread t (doAsyncWork) ; auto fut = std::async (doAsyncWork); auto result = fut.get ();
std::async 的优势 :
自动管理线程
可以获取返回值
可以传播异常
避免过度订阅
Item 36-40: 并发编程最佳实践
使用 std::atomic 而非 volatile :
1 2 3 4 5 6 7 8 9 10 11 volatile int counter = 0 ;void increment () { ++counter; } std::atomic<int > counter (0 ) ;void increment () { ++counter; }
避免在 std::atomic 上使用复制操作 :
1 2 3 4 5 6 7 std::atomic<int > x (0 ) ;auto y = x; auto y = x.load (); std::atomic<int > z (0 ) ;z = x; z.store (x.load ());
第八章:微调
Item 41: 对于可拷贝的形参,当移动成本低且总会被拷贝时,考虑按值传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Widget {public : void setName (const std::string& newName) { name = newName; } void setName (std::string&& newName) { name = std::move (newName); } template <typename T> void setName (T&& newName) { name = std::forward<T>(newName); } void setName (std::string newName) { name = std::move (newName); } private : std::string name; };
性能对比 :
调用方式
重载
万能引用
按值传递
左值
1次拷贝
1次拷贝
1次拷贝+1次移动
右值
1次移动
1次移动
1次移动+1次移动
Item 42: 考虑使用置入而非插入
1 2 3 4 5 6 7 8 9 10 11 std::vector<std::string> vs; vs.push_back ("xyzzy" ); vs.emplace_back ("xyzzy" ); vs.push_back (std::string (50 , 'x' )); vs.emplace_back (50 , 'x' );
何时使用置入 :
值被构造进容器,而非赋值
传递的参数类型与容器元素类型不同
容器不太可能拒绝新值(如 set)
总结
核心要点总结
graph TD
A[Effective Modern C++] --> B[类型推导]
A --> C[auto]
A --> D[移动语义]
A --> E[智能指针]
A --> F[Lambda]
A --> G[并发]
B --> B1[模板推导3种情况]
B --> B2[decltype陷阱]
C --> C1[优先使用auto]
C --> C2[注意代理类]
D --> D1[move无条件转右值]
D --> D2[forward条件转发]
D --> D3[完美转发失败情况]
E --> E1[unique_ptr独占]
E --> E2[shared_ptr共享]
E --> E3[weak_ptr观察]
F --> F1[避免默认捕获]
F --> F2[初始化捕获]
F --> F3[优先auto]
G --> G1[基于任务]
G --> G2[atomic不是volatile]