Effective C++

Effective Modern C++ 完全解读笔记汇总_loongknown的博客-CSDN博客

Item 1 deducing type

Case 1: ParamType is a Reference or Pointer, but not a Universal Reference

  1. If expr’s type is a reference, ignore the reference part.
  2. Then pattern-match expr’s type against ParamType to determine T.

Case 2: ParamType is a Universal Reference

1.If expr is an lvalue, both T and ParamType are deduced to be lvalue references.

2.If expr is an rvalue, the“normal” (i.e., Case 1) rules apply.

image-20221016154716671

Case 3: ParamType is Neither a Pointer nor a Reference

1.As before, if expr’s type is a reference, ignore the reference part.

2.If, after ignoring expr’s reference-ness, expr is const, ignore that, too. If it’s volatile, also ignore that.(指针不会忽略)

image-20221016160731037

Item 2 auto(函数也是一样的)都有衰退数组变指针

image-20221016180020611

So 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.

Item 3 decltype

  • decltype几乎总是能够得出变量或者表达式的类型。

  • 对于类型为 T 的左值表达式,而不是类型,decltype 基本上总是输出 T&

    int x=10;
    decltype(x);//int
    decltype((x));//int&
    
  • C++14支持 delctype(auto),像是 auto,能够自动从初始化列表中推断出类型,但它使用的是decltype 的推断规则。

deque<int> de={1,1,1,1};
decltype(de[0]) i=de[0];
i=2;
j=2;
cout<<de[0]<<endl;//2 是引用
auto m=de[0];
m=3;
cout<<de[0]<<endl;//2 不是引用

image-20221016212638796

Item 4: Know how to view deduced types.

#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';
…
}//查看变量数据类型

Item 5: Prefer auto to explicit type declarations.

什么是闭包
闭包有很多种定义,一种说法是,闭包是带有上下文的函数。说白了,就是有状态的函数。更直接一些,不就是个类吗?换了个名字而已。

一个函数,带上了一个状态,就变成了闭包了。那什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量,这些个变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。

函数是代码,状态是一组变量,将代码和一组变量捆绑 (bind) ,就形成了闭包。

内部包含 static 变量的函数,不是闭包, 因为这个 static 变量不能捆绑。你不能捆绑不同的 static 变量,这个在编译的时候已经确定了。

Item 6: Use the explicitly typed initializer idiom whenauto deduces undesired types.

vector<bool> vec({true,true});
auto nihao=vec[0];//vector<bool> 返回的是他的vector<bool>::reference特殊类型  所以auto把他转化为引用
nihao=false;
cout<<vec[0];//false 修改了vec中元素


vector<int> vec({1,1});
auto nihao=vec[0];//虽然[]返回的是引用但是auto会去掉引用
nihao=2;
cout<<vec[0];//1 未修改了vec中元素

Item 7: Distinguish between () and {} when creatingobjects.

尽量使用大括号进行初始化(但要注意initializer_lists,在使用构造函数时更倾向于使用initializer_lists(即使不那么匹配))

initializer_lists中禁止缩小范围转化 double无法转换成int 但是int可以转换为double

谨慎考虑为类添加initializer_list的构造函数

Item 8: Prefer nullptr to 0 and NULL.

在模板类型中 0和NULL有可能被deduce为int类型,而不是空指针导致转换失败

Item 9: Prefer alias declarations to typedefs.

即使用using来代替typedef声明

Item 10: Prefer scoped enums to unscoped enums.

enum Color { black, white, red };
//非作用域枚举
enum class Color { black, white, red };//作用域枚举

Item 11: Prefer deleted functions to private undefinedones

template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
…
basic_ios(const basic_ios& ) = delete;
basic_ios& operator=(const basic_ios&) = delete;
…
};

Item 12: Declare overriding functions override.

class Derived: public Base {
public:
virtual void mf1() override;
virtual void mf2(unsigned int x) override;
virtual void mf3() && override;
virtual void mf4() const override;
};

Item 15: Use constexpr whenever possible.

//constexpt和const区别
int sz; // non-constexpr variable
constexpr auto arraySize1 = sz;//error 
const auto arraySize = sz;//Simply put, all constexpr objects are const, but not all const objects are constexpr.constexpr编译时确定值

Item 16: Make const member functions thread safe.

Item 17: Understand special member functiongeneration.

The C++11 rules governing the special member functions are thus:
Default constructor: Same rules as C++98. Generated only if the class contains
no user-declared constructors.
Destructor: Essentially same rules as C++98; sole difference is that destructors
are noexcept by default (see Item 14). As in C++98, virtual only if a base class
destructor is virtual.
Copy constructor: Same runtime behavior as C++98: memberwise copy con‐
struction of non-static data members. Generated only if the class lacks a user-
declared copy constructor. Deleted if the class declares a move operation.
Generation of this function in a class with a user-declared copy assignment oper‐
ator or destructor is deprecated.
Copy assignment operator: Same runtime behavior as C++98: memberwise
copy assignment of non-static data members. Generated only if the class lacks a user-declared copy assignment operator. Deleted if the class declares a move
operation. Generation of this function in a class with a user-declared copy con‐
structor or destructor is deprecated.
Move constructor and move assignment operator: Each performs memberwise
moving of non-static data members. Generated only if the class contains no user-
declared copy operations, move operations, or destructor.

成员函数模板不存在上述约束

Item 18: Use std::unique_ptr for exclusive-ownershipresource management.

  • std::unique_ptr 是一个小的、快的、move-only 的智能指针,它能用来管理资源,并且独占资源的所有权。
  • 默认情况下,std::unique_ptr 资源的销毁是用 delete 进行的,但也可以用户自定义 deleter。用带状态的 deleter 和函数指针作为 deleter 会增加 std::unique_ptr 对象的大小。
  • 很容易将 std::unique_ptr 转换为 std::shared_ptr。

Item 19: Use std::shared_ptr for shared-ownershipresource management.

  • 如果一个unique_ptr对象在move时,源和目的unique_ptr对象的删除器类型也必须一样,这点与shared_ptr不一样。
  • std::shared_ptr 为任意共享所有权的资源提供一种自动垃圾回收的便捷方式。
  • 较之于 std::unique_ptr,std::shared_ptr 对象占用的内存通常大两倍,控制块会产生开销,需要原子引用计数修改操作。
  • 默认资源销毁是通过 delete,但是也支持自定义 deleter。自定义 deleter 的类型对 std::shared_ptr 的类型没有影响。
  • 避免从原始指针变量上创建 std::shared_ptr
  • 将构造函数声明为私有的,则只能在类内部定义静态成员函数来进行类的构造。

Item 20: Use std::weak_ptr for std::shared_ptr-like pointers that can dangle.

  • 1.对类似 std::shared_ptr 可能悬空的指针,使用 std::weak_ptr。
  • 2.std::weak_ptr 的潜在使用场景包括:caching、observer lists、避免 std::shared_ptr 的循环引用。

Item 21: Prefer std::make_unique andstd::make_shared to direct use of new.

使用new可能造成内存泄漏

processWidget(std::shared_ptr<Widget>(new Widget), computePriority());//先调用new此时还未调用构造函数 直接先调用compu函数  且发生意外程序终止  则new的空间没有释放  造成内存泄漏

std::shared_ptr spw(new Widget);//new进行了两次内存分配,一块存储本身对象一块存储控制块,

auto spw = std::make_shared<Widget>();//此方法分配一块内存保存对象和控制块  提高可执行代码速度  但是make构造智能指针不能添加删除函数

只有当shared_ptr和weak_ptr的引用计数都为0时,控制块才会被释放
    
传递右值只需要 move,而传递左值必须要拷贝

Item 22: When using the Pimpl Idiom, define specialmember functions in the implementation file.

在其生成默认函数时,unique必须指向完整类型,shared_ptr则可以指向不完整类型

// in "widget.h"
#include <memory>
class Widget {
public:
  Widget();
  ...
private:
  struct Impl;                 
  std::unique_ptr<Impl> pImpl;  // use smart pointer instead of raw pointer
};

//==================================================================================//
// in "widget.cpp"
#include "widget.h"  
#include "gadget.h"
#include <string>
#include <vector>

struct Widget::Impl {  // as before
  std::string name;
  std::vector<double> data;
  Gadget g1, g2, g3;
};

Widget::Widget()
: pImpl(std::make_unique<Impl>())
{}

#include "widget.h"
int main() {
  Widget w;
  return 0;
}  //会报错  虽然智能指针会自动析构指向的资源,但是未定义析构函数,则析构函数会在.h文件中生成,但是在.h文件中Impl是不完全类型(无法使用sizeof函数,默认unique_ptr的删除器中用到了),解决方案是加入析构函数。因为加入了析构函数所,根据3法则需要加入copy和赋值等函数。


//shared_ptr则没有,因为其删除器不属于其本身

Item 23: Understand std::move and std::forward.

//参数始终是左值 即使参数是右值引用
void f(Widget&& w);

//move在任何时候都会将lvalue转化为rvalue,而forward只会在传入参数的值是rvalue时才会将lvalue转化为rvalue
  • std::move 无条件将输入转化为右值。它本身并不移动任何东西。
  • std::forward 把其参数转换为右值,仅仅在参数被绑定到一个右值时。
  • std::move 和 std::forward 只是做类型转换,在运行时(runtime)不做任何

Item 24: Distinguish universal references from rvaluereferences.

//通用引用最常用的两个例子  没有类型推断则是右值引用
template<typename T>//函数模板参数 必须严格是T&&   const T&&不是  vector<T>&&也不是
void f(T&& param);

auto&& var2 = var1;//自动声明

//通用引用 給左值是左值引用 给右值是右值引用

template<typename T>
void f(std::vector<T>&& param); // param is an rvalue reference
  • 如果一个函数模板参数有 T&& 格式,并且发生类型推导,或者一个对象使用 auto&& 来声明,那么参数或对象就是一个万能引用。
  • 如果类型推导的格式不是准确的 T&&(type&&),或者如果类型推导没有发生,T&&(type&&)就是一个右值引用。
  • 如果用右值来初始化,万能引用相当于右值引用。如果用左值来初始化,则相当于左值引用。

Item 25: Use std::move on rvalue references, std::forward on universal references.

函数传入右值引用使用move,传入通用引用用forward

Widget makeWidget() // "Copying" version of makeWidget
{
 Widget w; // local variable
… // configure w
return w; // "copy" w into return value
}

Widget makeWidget() // Moving version of makeWidget
{
 Widget w;
…
return std::move(w); // move w into return value
}//C++有局部变量返回值优化  

Item 26: Avoid overloading on universal references.

  • 对万能引用参数的函数进行重载,调用机会将比你期望的多得多。
  • 完美转发构造函数是糟糕的实现,因为对于 non-const 左值不会调用拷贝构造而是完美转发构造,而且会劫持派生类对于基类的拷贝和移动构造的调用。

Item 27: Familiarize yourself with alternatives tooverloading on universal references.

  • 万能引用和重载的组合替代方案包括使用不同的函数名、通过 const 左值引用传参、按值传递参数,使用 tag 分发。
  • 通过 std::enable_if 约束模板来允许万能引用和重载组合使用,std::enable_if 可以控制编译器什么条件才使用万能引用的实例。
  • 万能引用参数通常具有高效率的优势,但通常可用性较差。

Item 28: Understand reference collapsing.

  • 引用折叠发生在四种情况:模板实例化,auto 类型的生成,创建和使用 typedef、别名声明和decltype。
  • 当编译器生成了引用的引用时,通过引用折叠就是单个引用。其中之一为左值引用就是左值引用,否则就是右值引用。
  • 在类型推导区分左值和右值以及引用折叠发生的上下文中,万能引用是右值引用

Item 29: Assume that move operations are not present,not cheap, and not used.

  • 假设移动操作不可用、不廉价。
  • 在已知类型或支持移动语义的代码中,不需要进行此假设

Item 30: Familiarize yourself with perfect forwardingfailure cases.

  • 当模板类型推导失败或者推导类型是错误的时候完美转发会失败。
  • 导致完美转发失败的类型有花括号初始化、空指针的 0 或者 NULL、只声明的整型 static const 数据成、,模板和重载的函数名和位域。

Item 31: Avoid default capture modes.

  • 默认的按引用捕获可能会导致引用悬挂。
  • 默认的按值引用对于悬挂指针很敏感(尤其是this指针),并且它会误导人认为 lambda 是独立的。(只能捕获非静态局部变量)

Item 32: Use init capture to move objects into closures.

  • C++14 使用初始化捕获模式实现移动捕获。
  • C++11 使用 std::bind 间接实现移动捕获。

Item 33: Use decltype on auto&& parameters to std::forward them

  • 对 auto&& 参数使用 decltype来转发(std::forward)。

Item 34: Prefer lambdas to std::bind

  • 相较于 std::bind,lambda 代码可读性更强、更容易理解、性能可能更好。
  • C++11 的 std::bind 在实现移动捕获、模板函数对象方面可以弥补 lambda 的不足。

Item 35: Prefer task-based programming to thread-based.

并发先不看

Item 41: Consider pass by value for copyable parameters that are cheap to move and always copied

C++11 传参时对左值进行复制构造 对右值进行移动构造

  • 对于可复制、移动开销低、且无条件复制的参数,按值传递效率基本与按引用传递效率一致,而且易于实现,生成更少的目标代码。
  • 通过构造函数拷贝参数可能比通过赋值拷贝开销大的多。
  • 按值传递会引起切片问题,不适合基类类型的参数。

Item 42: Consider emplacement instead of insertion.

  • 原则上,emplacement 函数会比传统插入函数更高效。
  • 实际上,当执行如下操作时,emplacement 函数更快:(1)值被构造到容器中,而不是直接赋值;(2)传入参数的类型与容器类型不一致;(3)容器不拒绝已经存在的重复值。
  • emplacement 函数可能执行类型转化,而传统插入函数会拒绝。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值