《Effective Modern C++》, Scott Meyers
《Effective Modern C++》是一本由Scott Meyers撰写的书籍,该书主要讨论了C++11和C++14(现代C++)中的关键特性和最佳实践,以帮助读者编写出更高效、更安全、更易维护的代码。
Scott Meyers是世界顶级的C++软件开发技术权威之一,他曾是C++ Report的专栏作家,并为C/C++ Users Journal和Dr. Dobb’s Journal等刊物撰稿。他还为全球范围内的客户提供咨询活动,并在NumeriX LLC和InfoCruiser公司的顾问委员会中担任成员。他拥有Brown University的计算机科学博士学位。
《Effective Modern C++》中文版由高博翻译,于2018年4月23日由中国电力出版社出版。该书豆瓣评分高达8.9,获得了读者的广泛好评。书中包含42条实用技巧,涵盖了从auto型别推导、移动语义、lambda表达式,到并发支持等多个主题,旨在帮助读者改善C++11和C++14的高效用法。
有读者评价该书“对C++11的很多细节了解的非常清楚”,但也指出“看这本书是需要门槛的,如果新手看这本会很懵逼”。因此,对于有一定C++编程基础的读者来说,这本书会是一本非常有价值的参考资料。
Intro
- lvalue: 和地址密切联系,all parameters are lvalues
- function object可以是定义了operator ()的object,只要有function-calling syntax就行
- deprecated feature:
std::auto_ptr -> std::unique_ptr
- Undefined behavior: 数组越界、dereferencing an uninitialized iterator、engaging in a data race
chpt 1 Deducing Types
在C++中,类型推导(Type Deduction)是一个重要的特性,它允许编译器自动推断出变量、函数参数等的类型。这个特性在C++的各个版本中都有所发展,从C++98的函数模板,到C++11的auto和decltype,再到C++14的decltype(auto),这些特性都极大地提高了代码的简洁性和可读性。
C++98: 函数模板
在C++98中,函数模板是最常见的类型推导方式。通过函数模板,我们可以编写一个通用的函数,该函数可以处理不同类型的参数。编译器会根据传递给函数的实际参数类型来推导模板参数的类型。
- c++98: function template
template<typename T>
void foo(T x) {
// ...
}
int main() {
foo(10); // T 被推导为 int
foo(3.14); // T 被推导为 double
return 0;
}
- c++11: auto, decltype
在C++11中,引入了auto关键字来进行类型推导。使用auto可以自动推断出变量的类型,而无需显式指定。这尤其适用于那些类型复杂或者类型由表达式结果决定的情况。
decltype是C++11中引入的另一个类型推导关键字。它允许我们查询一个表达式的结果类型,而不需要实际计算该表达式的值。这对于定义返回类型依赖于输入参数类型的函数特别有用。
auto x = 10; // x 是 int 类型
auto y = 3.14; // y 是 double 类型
auto z = foo(); // z 的类型由 foo() 的返回类型决定
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// 或者使用尾置返回类型(更简洁)
template<typename T, typename U>
auto add(T a, U b) {
return a + b;
}
int main() {
auto result = add(10, 3.14); // result 是 double 类型
return 0;
}
- c++14: decltype(auto)
在C++14中,引入了decltype(auto)作为auto的一个扩展。与auto不同,decltype(auto)会保留表达式的完整类型,包括引用和值类别(lvalue或rvalue)。这使得decltype(auto)在处理引用和移动语义时特别有用。
template<typename Container>
decltype(auto) front(Container&& c) {
return std::forward<Container>(c).front();
}
int main() {
std::vector<int> v = {
1, 2, 3};
auto& x = front(v); // x 是 int& 类型,因为 std::vector::front 返回引用
x = 42; // 修改 v 中的元素
return 0;
}
在这个例子中,decltype(auto)保留了std::vector::front的返回类型(即int&),这使得我们可以直接修改原始容器中的元素。如果使用auto,那么x将是一个值类型(即int),这将导致对原始容器中元素的修改无效。
Item 1: Understand template type deduction 、Constexpr
auto的deduction机制和template type deduction紧密相联
模版类型推断是综合性的,不止和T相关,也和ParamType相关(adornments),有三种情形
template<typename T>
void f(ParamType param);
f(expr); // deduce T and ParamType from expr
-
Case 1: ParamType is a Reference or Pointer, but not a Universal Reference
-
Case 2: ParamType is a Universal Reference
-
右值->右值引用;左值->左值引用
-
唯一T会deduce为引用的场景
-
-
Case 3: ParamType is Neither a Pointer nor a Reference
- Param will be a copy
- 去除 const、&、volatile等修饰
const char* const ptr = // ptr is const pointer to const object "Fun with pointers";
保留左边的const
- Param will be a copy
-
array arguments
- the type of an array that’s passed to a template function by value is deduced to be a pointer type
- 需要注意数组传引用的时候不一样!存在数组引用
constchar(&)[13]
=> 利用它来推断数组大小
// return size of an array as a compile-time constant. (The
// array parameter has no name, because we care only about
// the number of elements it contains.)
template<typename T, std::size_t N> // see info
constexpr std::size_t arraySize(T (&)[N]) noexcept // below on
{ // constexpr
return N; // and
}// noexcept
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has 7 elements
int mappedVals[arraySize(keyVals)]; // so does mappedVals
std::array<int, arraySize(keyVals)> mappedVals; // mappedVals' size is 7
static_assert(arraySize(keyVals) == 7);
类内定义
constexpr static double keyVals[] = {1,2,3,4,5,6,7}
Item 2: Understand auto type deduction.
原则:只有明确需要 copy 才使用 auto,其它需要 auto 的情况用 auto*, auto&
A& g(); auto a = g(); A a = g();
template<typename T>
void f(ParamType param);
f(expr); // deduce T and ParamType from expr
auto对应T,type specifier对应ParamType,因此同Item1,也有三个cases,但有一个exception
唯一的exception:auto x = { 20 };
the deduced type is a std::initializer_list,如果里面元素类型不一致,不会编译
- the only real difference between auto and template type deduction is that auto assumes that a braced initializer represents a std::initializer_list, but template type deduction doesn’t
Things to Remember
- auto type deduction is usually the same as template type deduction, but auto type deduction assumes that a braced initializer represents a
std::initializer_list
, and template type deduction doesn’t. - auto in a function return type or a lambda parameter implies template type deduction, not auto type deduction.
Item 3: Understand decltype.
In C++11, perhaps the primary use for decltype is declaring function templates where the function’s return type depends on its parameter types.
- the type returned by a container’s operator[] depends on the container,比如
vector<bool>
返回的不是引用- c++14甚至可以不需要->,用模版判断,但存在无法引用的问题
template<typename Container, typename Index> // works, but requires
auto authAndAccess(Container& c, Index i) // refinement
-> decltype(c[i])
{
authenticateUser();
return c[i];
}
template<typename Container, typename Index> // C++14; works,
decltype(auto) // but still requires
authAndAccess(Container& c, Index i) // refinement
{
authenticateUser();
return c[i];
}
- decltype(auto)利用decltype rule,帮助单独的auto返回引用
- 本质上是因为auto难以识别type specifier
- decltype((x)) is therefore int&. Putting parentheses around a name can change the type that decltype reports for it!
Item 4: Know how to view deduced types.
template<typename T> // declaration only for TD;
class TD; // TD == "Type Displayer"
TD<decltype(x)> xType; // elicit errors containing
TD<decltype(y)> yType; // x's and y's types
///
std::cout << typeid(x).name() << '\n'; // display types for std::cout << typeid(y).name() << '\n'; // x and y
std::type_info::name
对模版中变量类型的判断不准
#include <boost/type_index.hpp>
template<typename T>
void f(const T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
// show T
cout << "T = "<< type_id_with_cvr<T>().pretty_name() << '\n';
// show param's type
cout << "param = "<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
chpt 2 auto
Item 5: Prefer auto to explicit type declarations.
template<typename It>
void dwim(It b, It e)
{
while (b != e) {
// algorithm to dwim ("do what I mean")
// for all elements in range from
// b to e
typename std::iterator_traits<It>::value_type
currValue = *b;
}
}
auto必须初始化,潜在地避免未初始化的问题
auto derefUPLess =
[](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// C++14内部也可用auto
std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)>
derefUPLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// std::function相比auto占用内存多,效率低(对象统一进行了转化)
Item 6: Use the explicitly typed initializer idiom when auto deduces undesired types.
有关vector<bool>
:because operator[] for std::vector<T>
is supposed to return a T&, but C++ forbids references to bits.
std::vector<bool>::reference
is an example of a proxy class: a class that exists for the purpose of emulating and augmenting the behavior of some other types
proxy class
- invisible:
vector<bool>
- apparent:
std::shared_ptr, std::unique_ptr
- expression templates:
Matrix sum = m1 + m2 + m3 + m4;
chpt 3 Moving to Modern C++
Item 7: Distinguish between () and {} when creating objects.
std::vector<int> v{ 1, 3, 5 }; // v's initial content is 1, 3, 5
特性:
- uncopyable objects (e.g., std::atomics—see Item 40)
std::atomic<int> ai1{ 0 }; // fine
- it prohibits implicit narrowing conversions among built-in types
- immunity to C++’s most vexing parse
- A side effect of C++’s rule that anything that can be parsed as a declaration must be interpreted as one
缺点:
-
the unusually tangled relationship among braced initializers, std::initializer_lists, and constructor overload resolution
Widget(std::initializer_list<long double> il);
对{}的优先级极高,只有在实在转换不了的情况下才会用non-std::initializer_list constructors- Empty braces mean no arguments, not an empty std::initializer_list
- vector对{}和()初始化的区分是不合理的
- 因此:重载initializer_list参数的函数尤其要谨慎
Item 8: Prefer nullptr to 0 and NULL.
- 0和NULL混用带来函数重载问题 -> counterintuitive behavior
- nullptr’s advantage is that it doesn’t have an integral type, but you can think of it as a pointer of all types
- NULL和0在template type deduction中问题更严重
Item 9: Prefer alias declarations to typedefs.
template<typename T> // MyAllocList<T>
using MyAllocList = std::list<T, MyAlloc<T>>; // is synonym for
// std::list<T,
// MyAlloc<T>>
MyAllocList<Widget> lw; // client code
template<typename T> // MyAllocList<T>::type
struct MyAllocList { // is synonym for std::list<T, MyAlloc<T>>
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw; // client code
template<typename T>
class Widget {
private:
typename MyAllocList<T>::type list;
...
};
using的优势是能让编译器知道这是一个type,尤其在模版的使用中,避免混淆MyAllocList<T>::type
template metaprogramming (TMP)
- C++11中的type traits是用typedef实现的,因此使用的时候要在前面声明
typename
std::remove_const<T>::type // yields T from const T
std::remove_reference<T>::type // yields T from T& and T&&
std::add_lvalue_reference<T>::type // yields T& from T
std::remove_const_t<T>
std::remove_reference_t<T>
std::add_lvalue_reference_t<T>
template <class T>
using remove_const_t = typename remove_const<T>::type;
template <class T>
using remove_reference_t = typename remove_reference<T>::type;
template <class T>
using add_lvalue_reference_t =
typename add_lvalue_reference<T>::type;
Item 10: Prefer scoped enums to unscoped enums.
enum class Color { black, white, red };
auto c = Color::white;
enum Color; // error!
enum class Color; // fine
=> The reduction in namespace pollution
=> There are no implicit conversions from enumerators in a scoped enum to any other type
=> scoped enums may always be forward-declared, unscoped enums只有指定了underlying type才可以
- 普通的enum默认是char,编译器需要选类最小的类型=> unscoped enums不需要大部分重新编译
- Scoped enums默认类型int,可指定
enum class Status: std::uint32_t; // underlying type for
// Status is std::uint32_t
// (from <cstdint>)
unscoped enum更好的少数场景,uiEmail有implicit转换为size_t
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
...
auto val = std::get<uiEmail>(uInfo); // ah, get value of // email field
enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
...
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);
如果用函数转,需要constexpr
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumerator) noexcept
{
return static_cast<typename std::underlying_type<E>::type>(enumerator);
}
c++14可以用 static_cast<std::underlying_type_t<E>>(enumerator)
template<typename E> // C++14
constexpr auto
toUType(E enumerator) noexcept
{
return static_cast<std::underlying_type_t<E>>(enumerator);
}
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
Item 11: Prefer deleted functions to private undefined ones.
e.g.: std::basic_ios,禁止copy streams
相比undefined private function
-
错误信息更清晰
-
可以delete非成员函数
-
针对模版
template<typename T>
void processPointer(T* ptr);
template<>
void processPointer<void>(void*) = delete;
template<>
void processPointer<char>(char*) = delete;
template<>
void processPointer<const void>(const void*) = delete;
template<>
void processPointer<const char>(const char*) = delete;
// const volatile char*, const volatile void*, std::wchar_t, std::char16_t, std::char32_t.
//如果在类内定义
template<>
void Widget::processPointer<void>(void*) = delete;
Item 12: Declare overriding functions override.
class Base {
public:
virtual void doWork();
...
};
class Derived: public Base {
public:
virtual void doWork();
...
};
std::unique_ptr<Base> upb = std::make_unique<Derived>();
upb->doWork();
// derived class function is invoked
-
override的各种要求,还包括reference qualifier
-
改变signature方便看有多少依赖
-
Applying final to a virtual function prevents the function from being overridden in derived classes. final may also be applied to a class, in which case the class is prohibited from being used as a base class.
-
Member function reference qualifiers make it possible to treat lvalue and rvalue objects (*this) differently.
class Widget {
public:
using DataType = std::vector<double>;
DataType& data() & { return values; }
DataType data() && { return std::move(values); } ...
private:
DataType values;
};
Item 13: Prefer const_iterators to iterators.
pointer-to-const == const_iterators
there’s no portable conversion from a const_iterator
to an iterator
, not even with a static_cast
. Even the semantic sledgehammer known as reinterpret_cast
can’t do the job.
std::vector<int> values; // as before ...
auto it = // use cbegin
std::find(values.cbegin(),values.cend(), 1983); // and cend
values.insert(it, 1998);
template<typename C, typename V>
void findAndInsert(C& container,
{
const V& targetVal,
const V& insertVal)
using std::cbegin;
using std::cend;
auto it = std::find(cbegin(container), cend(container),
targetVal);
container.insert(it, insertVal);
}
template <class C>
auto cbegin(const C& container)->decltype(std::begin(container)) {
return std::begin(container); // see explanation below
}
Non-member cbegin只有C++14才有;C++11中,即使是vector非const,begin返回的带const引用的vector也会是const_iterator类型
Item 14: Declare functions noexcept if they won’t emit exceptions.
noexcept意味着函数行为const
e.g. C++11,vector.push_back(std::move(XX))
可能违反异常安全性(比如vector重新申请内存的场景),std::vector::push_back
takes advantage of this “move if you can, but copy if you must” strategy,依据是否declared noexcept来判断
- 内部实现->
std::move_if_noexcept
->std::is_nothrow_move_constructible
template <class T, size_t N>
void swap(T (&a)[N], // see
T (&b)[N]) noexcept(noexcept(swap(*a, *b))); // below
template <class T1, class T2>
struct pair {
...
void swap(pair& p) noexcept(noexcept(swap(first, p.first)) && noexcept(swap(second, p.second)));
...
};
- Wide contract functions更适合noexcept,因为没有后续调试抛出异常的需求
Things to Remember
• noexcept is part of a function’s interface, and that means that callers may depend on it.
• noexcept functions are more optimizable than non-noexcept functions.
• noexcept is particularly valuable for the move operations, swap, memory deallocation functions, and destructors.
• Most functions are exception-neutral rather than noexcept.
Item 15: Use constexpr whenever possible.
-
translation时知晓,包括compilation和linking
-
constexpr变量声明时需要初始化
- 类内constexpr变量需要额外定义
- https://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char
constexpr int A::n;
in cpp file
- 类内constexpr变量需要额外定义
-
constexpr function,用法参考Item 1
- c++11只能写一句return
constexpr
int pow(int base, int exp) noexcept
{
...
}
constexpr auto numConds = 5;
std::array<int, pow(3, numConds)> results;
constexpr int pow(int base, int exp) noexcept //c++11
{
return (exp == 0 ? 1 : base * pow(base, exp - 1));
}
constexpr int pow(int base, int exp) noexcept
{
auto result = 1;
for (int i = 0; i < exp; ++i) result *= base;
return result;
}
P100: 自定义的类型也可以constexpr
- C++14中即使是Point.set也可以constexpr
Item 16: Make const member functions thread safe.
class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const
{
std::lock_guard<std::mutex> g(m);
if (!rootsAreValid) {
...
rootsAreValid = true;
}
return rootVals;
}
private:
mutable std::mutex m;
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
私有成员mutable std::atomic<unsigned> callCount{ 0 };
适合用于计数
- std::mutex和std::atomic都是move-only types,不能copy
- 如果想定义一个数组,数组中的对象包含atomic类型,不能用vector直接构造(因为会调用复制构造函数),可以构造
vector<shared_ptr<Object>>
- atomic适合递增操作,但如果是先运算后赋值,可能出现竞争
- mutable 字段,表示“这个成员变量不算对象内部状态”,不禁止const函数修改它
- 如果想定义一个数组,数组中的对象包含atomic类型,不能用vector直接构造(因为会调用复制构造函数),可以构造
class Widget {
public:
...
int magicValue() const
{
std::lock_guard<std::mutex> guard(m);
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = val1 + val2;
cacheValid = true;
return cachedValue;
}
}
private:
mutable std::mutex m;
mutable int cachedValue;
mutable bool cacheValid{ false };
};
Item 17: Understand special member function generation
问题的来源:memberwise move的思路,move能move的,剩下的copy
special member functions:
- c++98: the default constructor, the destructor, the copy constructor, and the copy assignment operator
- c++11: the move constructor and the move assignment operator
- 和copy constructor的区别在于,c++11的这两个fucntion是dependent的
- 如果有explicit copy constructor,move constructor不会自动生成
the rule of three: copy constructor, copy assignment operator, or destructor
动机:the rule of three 和 move/copy 的 dependent 特性有冲突 => C++11 does not generate move operations for a class with a user-declared destructor
=default
和=delete
,前者在自己有写构造函数的情况下生成默认构造函数,减小代码量,后者禁止函数
//! moving is allowed; copying is disallowed; default construction not possible
//!@{
~TCPConnection(); //!< destructor sends a RST if the connection is still open
TCPConnection() = delete;
TCPConnection(TCPConnection &&other) = default;
TCPConnection &operator=(TCPConnection &&other) = default;
TCPConnection(const TCPConnection &other) = delete;
TCPConnection &operator=(const TCPConnection &other) = delete;
//!@}
Note: Member function templates never suppress generation of special member functions.
chpt 4 Smart Pointers
Item 18: Use std::unique_ptr for exclusive-ownership resource management.
- unique_ptr is a move-only type
- also applied to the Pimpl Idiom (Item 22)
- 还有
std::unique_ptr<T[]>
- About the only situation I can conceive of when a
std::unique_ptr<T[]>
would make sense would be when you’re using a C-like API that returns a raw pointer to a heap array that you assume ownership of.
- About the only situation I can conceive of when a
std::shared_ptr<Investment> sp = makeInvestment( arguments );
class Investment {
public:
virtual ~Investment();
};
class Stock:
public Investment { ... };
class Bond:
public Investment { ... };
class RealEstate:
public Investment { ... };
template<typename... Ts> std::unique_ptr<Investment> makeInvestment(Ts&&... params);
{
auto pInvestment = // pInvestment is of type
makeInvestment( arguments ); // std::unique_ptr<Investment>
}// destroy *pInvestment
auto delInvmt = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)> // 如果是C++14,返回值可以用auto,delInvmt的定义也可放入makeInvestment函数内
makeInvestment(Ts&&... params) {
std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
if ( /* a Stock object should be created */ ){
pInv.reset(new Stock(std::forward<Ts>(params)...));
}
else if ( /* a Bond object should be created */ ){
pInv.reset(new Bond(std::forward<Ts>(params)...));
}
else if ( /* a RealEstate object should be created */ ){
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
}
return pInv;
}
代码细节:
std::forward
: 为了在使用右值引用参数的函数模板中解决参数的perfect-forward问题。(Item 25), reference- unique_ptr可以作为函数返回值
- 不能直接给unique_ptr赋值裸指针,
reset(new)
是标准用法 - 引入deleter后unique_ptr的size变化:如果是函数指针,1 word -> 2 word;如果是函数对象,取决于函数内部的state
Item 19: Use std::shared_ptr for shared-ownership resource management.
dynamically allocated control blocks, arbitrarily large deleters and allocators, virtual function machinery, and atomic reference count manipulations
shared_ptr的使用, 一篇看不太懂的shared_ptr使用技巧
As with garbage collection, clients need not concern themselves with managing the life‐ time of pointed-to objects, but as with destructors, the timing of the objects’ destruc‐ tion is deterministic.
reference count的影响:
- std::shared_ptrs are twice the size of a raw pointer
- Memory for the reference count must be dynamically allocated,除非用
std::make_shared
- Increments and decrements of the reference count must be atomic
=> move assignment is faster than copy assignment
对于custom deleter,和unique_ptr的区别在于deleter不影响shared_ptr的类型
- 这样便于函数传参、放入同一容器
- 始终two words size:
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);
// deleter type is not part of ptr type
关于control block的rules
- std::make_shared (see Item 21) always creates a control block
- A control block is created when a std::shared_ptr is constructed from a unique-ownership pointer (i.e., a std::unique_ptr or std::auto_ptr).
- When a std::shared_ptr constructor is called with a raw pointer, it creates a control block
=>
- 用同一个裸指针创建两个shared_ptr是undefined behaviour
- 正确写法:
std::shared_ptr<Widget> spw1(new Widget);
std::shared_ptr<Widget> spw2(spw1);
-
类似地,不要用this指针创建shared_ptr
- 正确写法:The Curiously Recurring Template Pattern (CRTP)
- 注意 shared_from_this() 必须在对象生成 shared_ptr 后调用
- 为了防止process一个不存在shared_ptr的对象,常把ctors设成private
- shared_from_this() 不能在构造函数里调用,在构造时它还没有交给 shared_ptr 接管
class Widget: public std::enable_shared_from_this<Widget> {
public:
// factory function that perfect-forwards args to a private ctor
template<typename... Ts>
static std::shared_ptr<Widget> create(Ts&&... params);
void process(){
processedWidgets.emplace_back(shared_from_this());
}
private:
Widget Widget();
Widget(Widget &&other);
Widget(const Widget &other);
TCPConnection() = delete;
};
Warn:
std::vector<std::shared_ptr<A> > a_vec(n, std::make_shared<A>());
这个用法是错的,会给里面元素赋值同一个智能指针,正确写法如下:
std::vector<std::shared_ptr<A>> a(size);
std::for_each(std::begin(a), std::end(a),[](std::shared_ptr<A> &ptr) {
ptr = std::make_shared<A>();
- shared_ptr没有array的版本。一律建议用std::vector
- shared_ptr不能循环指向,会永不析构内存泄漏
Item 20: Use std::weak_ptr for std::shared_ptr-like pointers that can dangle.
weak_ptr指示shared_ptr是否dangle,但不能直接dereference,需要一个原子操作(lock),衔接判断和取值
- Potential use cases for
std::weak_ptr
include caching, observer lists, and the prevention of std::shared_ptr cycles.
auto spw = std::make_shared<Widget>();
std::weak_ptr<Widget> wpw(spw);
spw = nullptr;
if (wpw.expired()) ...
auto spw2 = wpw.lock();
std::shared_ptr<Widget> spw3(wpw); // if wpw's expired, throw std::bad_weak_ptr
应用:读写cache,自动删去不再使用的对象
std::unique_ptr<const Widget> loadWidget(WidgetID id);
=>
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;
}
//优化空间:删除cache内不用的weak_ptr
Item 21: Prefer std::make_unique and std::make_shared to direct use of new.
https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
C++14没有make_unique
- 参数只能是 unbounded array:
std::unique_ptr<Vec3[]> v3 = std::make_unique<Vec3[]>(5);
// C++14 make_unique
namespace detail {
template<class>
constexpr bool is_unbounded_array_v = false;
template<class T>
constexpr bool is_unbounded_array_v<T[]> = true;
template<class>
constexpr bool is_bounded_array_v = false;
template<class T, std::size_t N>
constexpr bool is_bounded_array_v<T[N]> = true;
} // namespace detail
template<class T, class... Args>
std::enable_if_t<!std::is_array<T>::value, std::unique_ptr<T>>
make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
std::enable_if_t<detail::is_unbounded_array_v<T>, std::unique_ptr<T>>
make_unique(std::size_t n)
{
return std::unique_ptr<T>(new std::remove_extent_t<T>[n]());
}
template<class T, class... Args>
std::enable_if_t<detail::is_bounded_array_v<T>> make_unique(Args&&...) = delete;
std::allocate_shared
auto spw1(std::make_shared<Widget>()); //能用auto
make_shared
优点
- 不用的话,有潜在的内存泄漏风险,和异常安全性有关
- 先new再创建shared_ptr,如果在这中间computePriority抛出异常,则内存泄漏
processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
make_shared
更高效,一次分配对象和control block的内存
make_shared
的缺点
- deleter
make_shared
的perfect forwarding code用()
,而非{}
;{}
只能用new,除非参考Item 2
// create std::initializer_list
auto initList = { 10, 20 };
// create std::vector using std::initializer_list ctor
auto spv = std::make_shared<std::vector<int>>(initList);
- using make functions to create objects of types with class-specific versions of operator new and operator delete is typically a poor idea. 因为只能new/delete本对象长度的内存,而非加上control block的
- shared_ptr的场景,必须要所有相关weak_ptr全destroy,指针控制块的内存才会释放
因为上述原因,想用new
std::shared_ptr<Widget> spw(new Widget, cusDel);
processWidget(std::move(spw), computePriority()); // both efficient and exception safe
Item 22: When using the Pimpl Idiom, define special member functions in the implementation file.
设计模式:减少client(只include .h文件)的相关编译
widget.h
class Widget {
public:
Widget();
~Widget();
...
private:
struct Impl;
Impl *pImpl;
};
widget.cpp
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() : pImpl(new Impl) {}
Widget::~Widget() { delete pImpl; }
新的用unique_ptr
的版本
- 不要在.h文件里generate析构函数
widget.h
class Widget {
public:
Widget();
~Widget();
Widget(Widget&& rhs);
Widget& operator=(Widget&& rhs);
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
widget.cpp
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget(): pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&& rhs) = default;
Widget& Widget::operator=(Widget&& rhs) = default;
Widget::Widget(const Widget& rhs)
: pImpl(std::make_unique<Impl>(*rhs.pImpl)) {}
Widget& Widget::operator=(const Widget& rhs)
{
*pImpl = *rhs.pImpl;
return *this;
}
如果是shared_ptr,则不需要做任何操作,本质上是因为unique_ptr的deleter在类型定义中,为了更高效的runtime行为,隐性地generate一些function
chpt 5 Rvalue References, Move Semantics, and Perfect Forwarding
Rvalue references => Move Semantics and Perfect Forwarding
it’s especially important to bear in mind that a parameter is always an lvalue, even if its type is an rvalue reference
Item 23: Understand std::move and std::forward.
std::move
doesn’t move anything. std::forward
doesn’t forward anything
std::move
unconditionally casts its argument to an rvalue, while std::forward
performs this cast only if a particular condition is fulfilled
template<typename T>
decltype(auto) move(T&& param) {
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
} // C++14
// remove_reference是因为Item 1 => T有可能被推断为lvalue reference