Effective Modern C++ Scott Meyers 类型推导 Type Deduction 《Effective STL》 vector and string

《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
  • 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.
  1. 0和NULL混用带来函数重载问题 -> counterintuitive behavior
  2. nullptr’s advantage is that it doesn’t have an integral type, but you can think of it as a pointer of all types
  3. 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 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函数修改它
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.
  • 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
  • 36
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EwenWanW

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值