effective modern C++——阅读笔记
文章平均质量分 84
effective modern C++的阅读笔记
干干干就完了
这个作者很懒,什么都没留下…
展开
-
条款42.考虑置入而非插入
考虑置入而非插入如果你有个容器,持有一些,比如说,std::string类型的对象,那么似乎合乎逻辑的做法是经由某个插入函数来向其添加新元素,而你传递给函数的元素类型将是std::string。说合乎逻辑倒也确实合乎逻辑,但却不一定合乎事实。std::vector<std::string> vs;vs.push_back("xyzzy"); //添加字符串字面量这里,容器持有的是std::string类型对象,但是你手头上有的是个字符串字面量。字符串字面量不是std::str原创 2021-05-18 09:17:16 · 365 阅读 · 0 评论 -
条款41.针对可复制的形参,在移动成本低并且一定会被拷贝的前提下,考虑将其按值传递
针对可拷贝的形参,在移动成本低并且一定会被拷贝的前提下,考虑将其按值传递有些函数的形参本来就是打算拿来复制的。成员函数addName可能会将其形参复制入其私有容器。这样的函数应该针对左值实施拷贝,针对右值实参实施移动。class Widget{public: void addName(const std::string& newName) { names.push_back(newName); } void addName(std::string&&a原创 2021-05-18 09:16:06 · 205 阅读 · 0 评论 -
条款34.优先选用lambda式而非bind
优先选择lambda式,而非std::bindstd::bind是C++98中std::bind1st和std::bind2nd的后继特性,但是,作为一种非标准特性而言,std::bind在2005年就已经是标准库的组成部分了。正是在那是,标准委员会接受了名称TR1的文档,里面就包含了std::bind的规格(在TR1中,bind位于不同的名字空间,所以是std::tr1::bind而非std::bind)。在C++11中,相对于std::bind,lambda几乎总会是更好的选择。到了C++14,la原创 2021-05-18 09:15:11 · 325 阅读 · 0 评论 -
条款33.对auto&&类型的形参使用decltype,以forward之
对于std::forward的auto&&使用decltype泛型lambda式是C++14最振奋人心的特性之一——lambda可以在形参规格中使用auto。这个特性的实现十分直截了当。闭包类中的operator()可以在形参规格中使用auto。例如,给定下述lambda式auto f = [](auto x){ return func(normalize(x)); };则闭包类的函数调用运算符如下所示class SomeCompilerGeneratedClassName{pu原创 2021-05-18 09:14:30 · 400 阅读 · 0 评论 -
条款32.使用初始化捕获将对象移入闭包
使用初始化捕获将对象移入闭包如果你想要把一个只能被移动的对象(例如std::unique_ptr或std::future类型的对象)放入闭包,C++11是无法实现的。如果你要拷贝的对象拷贝开销非常高,但移动的成本却不高(例如标准库中的大多数容器),并且你希望的的是移动该对象,而非拷贝它。但是,C++11中也还是没有让你实现这一点的途径。如果你的编译器支持C++14,那又是另一回事了。它能支持将对象移动到闭包中。缺少移动捕获被认为是C++11的一个缺点。最直接的补救措施本是在C++14中添加这一特性,但转载 2021-05-17 09:42:37 · 188 阅读 · 0 评论 -
条款31.避免默认捕获模式
lambda表达式lambda表达式,是表达式的一种,它是源代码组成部分。std::find_if(container.begin(), container.end(), [](int val){ return 0 < val && val < 10; });闭包是lambda创建的运行期对象,根据不同的捕获模式,闭包会持有数据的副本或引用。在上面的例子中,闭包就是作为第三个实参在运行期传递给std::find_if的对象。4闭包类就是实例转载 2021-05-17 09:42:05 · 460 阅读 · 0 评论 -
条款30.熟悉完美转发的失败情形
熟悉完美转发的失败情形完美转发的确切含义:“转发”的含义不过是一个函数把自己的形参传递(转发)给另一个函数而已。其目的是为了让第二个函数(转发目的函数)接受第一个函数(转发发起函数)所接受的同一对象。这就排除了按值传递形参,因为它们只是原始调用者所传递之物的副本,我们想要转发目的函数能够处理原始传入对象。指针形参也只能出局,因为我们不想强迫调用者传递指针。论及一般意义上的转发时,都是在处理形参为引用类型的情形。完美转发的含义是我们不仅转发对象,还转发显著特征:类型,是左值还有右值,以及是否带有const转载 2021-05-17 09:41:34 · 494 阅读 · 0 评论 -
条款29.假定移动操作不存在,成本高,未使用
假定移动操作不存在,成本高,未使用让我们从为何许多类型不能支持移动语义的观察开始。整个C++98标准库都已为C++11彻底翻新过,为很多类型提供了移动的能力,这些类型的移动实现比复制操作更快,并且对库的组件实现修改以利用移动操作。不过问题在于你有可能受伤的代码并未完成修订以充分利用C++11的良好特性。若你的应用中的类型没有为C++11做过专门修改,那么仅仅在编译器中有着对移动操作的支持也并不会给你带来什么明显好处。诚然,C++11愿意为这些缺少移动操作的类创建移动操作,但这也仅适用于那些未声明拷贝操作,转载 2021-05-17 09:40:55 · 125 阅读 · 0 评论 -
条款28.理解引用折叠
理解引用折叠以下面这个模板为例template<typename T>void func(T&& param);模板形参T的推导类型中,会把传给param的实参是左值还是右值的信息给编码进去。编码机制是直截了当的:如果传递的实参是个左值,T的推导结果就是个左值引用类型;如果传递的实参是个右值,T的推导结果就是非引用类型。Widget widgetFactory(); //返回右值的函数Widget w; //变量(左值)func(w); //调用func并转载 2021-05-17 09:40:24 · 1229 阅读 · 2 评论 -
条款27.熟悉万能引用类型进行重载的替代方案
熟悉万能引用重载的替代方法舍弃重载条款26的第一个例子,logAndAdd可以作为很多函数的代表,这样的函数只需要把本来打算进行重载的版本重新命名成不同的多个名字就可以避免对万能引用类型进行重载。以logAndAdd的两个重载版本为例,就可以分别改称logAndAddName和logAndAddNameIdx。传递const T& 类型的形参一种替代方式是回归C++98,使用传递左值常量引用类型来代替传递望能引用类型。这种方法的缺点是达不到我们想要的高效率。传值一种经常能够提升性能,却不转载 2021-05-17 09:39:54 · 317 阅读 · 0 评论 -
条款26.避免依万能引用类型进行重载
避免在万能引用类型上进行重载假定你需要撰写一个函数,取用一个名字作为形参,然后记录下当前日期和时间,再把该名字添加到一个全局数据结构中。可能你一开始拿出来的函数有点像下面这个std::multiset<std::string> names; //全局数据结构void logAndAdd(const std::string& name){ auto now = std::chrono::system_clock::now(); //取得当前时间转载 2021-05-17 09:39:17 · 184 阅读 · 0 评论 -
条款25.针对右值引用实施move,针对万能引用实施forward
针对右值引用实施std::move,针对万能引用实施std::forward右值引用仅会绑定到那些可供移动的对象上。如果形参类型为右值引用,则应该清楚地了解,它绑定的对象可供移动。class Widget{ Widget(Widget&& rhs); //rhs会绑定到可以用于移动目的的对象 ...};既然如此,你可能会希望把这些对象传递给其他函数时,使用一种允许该函数利用该对象的右值性的方式。手法就是,把绑定到了这些对象的形参转换为右值。class Widget{p转载 2021-05-17 09:38:39 · 195 阅读 · 0 评论 -
条款24.区分万能引用和右值引用
区分万能引用和右值引用void f(Widget&& param); //右值引用Widget&& var1 = Widget(); //右值引用auto&& var2 = var1; //非右值引用template<typename T>void f(std::vector<T>&& param); //右值引用template<typename T>void f(T&&转载 2021-05-17 09:38:08 · 380 阅读 · 0 评论 -
条款23.理解move和forward
理解std::move和std::forwardstd::move并不进行任何移动,std::forward也不进行任何转发,这两者在运行期都无所作为。它们不会生成任何可执行代码,连一个字节都不会生成。std::move和std::forward都是仅仅执行强制类型转换的函数(其实是函数模板)。std::move无条件地将实参强制转换成右值,而std::forward则仅在某个特定条件满足时才执行同一个强制转换。这里有个C++11中std::move的示例实现。template<typenam转载 2021-05-16 11:03:06 · 712 阅读 · 0 评论 -
条款22.当使用Pimpl习惯用法时,将特殊成员函数的定义放到实现文件中
使用Pimpl习惯用法时,将特殊成员函数的定义放到实现文件中Pimpl用法(“Pimpl”意为“Pointer to implementation”,即指向实现的指针)。这种技巧就是把某类的数据成员用一个指向到某实现类(或结构体)的指针替代,然后把原来在主类中的数据成员放到实现类中,并通过指针间接访问这些数据成员。例如,考虑Widget类如下class Widget{ //仍位于头文件“Widget.h”内public: Widget(); ~Widget(); private:转载 2021-05-16 11:02:36 · 188 阅读 · 0 评论 -
条款21.优先选择make_unique和make_shared,而非直接使用new
优先选用std::make_unique和std::make_shared,而非直接 使用newstd::make_shared是C++11的一部分,但是std::make_unique不是,它是在C++14中加入标准库的。如果你在C++11中,不必担心,写一个基础版本的std::make_unique易如反掌。template<typaname T, typename... Ts>std::unique_ptr<T> make_unique(Ts&&... p转载 2021-05-16 11:01:16 · 883 阅读 · 0 评论 -
条款20.对于shared_ptr但有可能空悬的指针使用weak_ptr
对于类似std::shared_ptr但有可能空悬的指针使用std::weak_ptr如果有一个像std::shared_ptr的指针但是不参与资源所有权共享的指针是很方便的。换句话说,是一个类似std::shared_ptr但不影响对象引用计数的指针。这种类型的智能指针必须要解决一个std::shared_ptr不存在的问题:可能指向已经销毁的对象。一个真正的智能指针应该跟踪所指对象,在悬空时知晓,悬空指针就是指针指向的对象不再存在。这就是对std::weak_ptr最精确的描述。std::weak_转载 2021-05-16 11:00:54 · 428 阅读 · 0 评论 -
条款19.对于共享资源使用shared_ptr
对于共享资源使用std::shared_ptrstd::shared_ptr指针访问的对象采用共享所有权来管理其生存期。没有特定的std::shared_ptr拥有该对象。相反,所有指向它的std::shared_ptr都能相互合作确保在它不再使用的那个点进行析构。当最后一个std::shared_ptr到达那个点,std::shared_ptr会销毁它所指向的对象。就垃圾回收而言,客户端不需要关心指向对象的声明周期,而对象的析构是确定的。std::shared_ptr通过引用计数来确保它是否是最后一个转载 2021-05-16 10:59:48 · 412 阅读 · 0 评论 -
条款18.使用std::unique_ptr管理具备专属所有权的资源
使用std::unique_ptr管理具备专属所有权的资源每当你需要使用智能指针的时候,std::unique_ptr基本是最合适的。可以认为在默认情况下,std::unique_ptr等同于原始指针,而且对于大多数操作(包括解引用),它们执行的指令完全相同。这意味着你甚至可以在内存和时间都比较紧张的情况下使用它。如果原始指针够小够快,那么std::unique_ptr几乎可以肯定也能满足你的要求。std::unique_ptr体现了专有所有权语义。一个非空的std::unique_ptr总是拥有其所指转载 2021-05-16 10:59:15 · 300 阅读 · 0 评论 -
条款17.理解特殊成员函数的生成
理解特殊成员函数的生成在C++术语中,特殊成员函数是指C++自己生成的函数。C++98有4个:默认构造函数,析构函数,拷贝构造函数,拷贝复制运算符。这些函数仅在需要的时候才生成。比如某个代码使用它们但是它们没有在类中声明。默认构造仅在类完全没有构造函数的时候才生成。生成的特殊成员函数是隐式public且inline,除非该类是继承自某个具有虚函数的类,否则生成的析构函数是非虚的。C++11特殊成员函数新增了:移动构造函数和移动赋值运算符。它们的签名是:class Widget{public:转载 2021-05-16 10:58:29 · 179 阅读 · 0 评论 -
条款15.只要有可能使用constexpr,就使用它
只要有可能使用constexpr,就使用它constexpr表明的值不仅仅是常量,还是编译期可知的。这个表述并不全面,因为当constexpr被用于函数的时候,事情就有一些差别了。constexpr对象和const对象一样,它们是编译期可知的。编译期可知的值,可能被存放到只读存储空间中。编译期可知的整数常量会出现在需要“整型常量表达式”的语境中,这类语境包括数组大小,整数模板参数(包括std::array对象的长度),枚举量,对齐修饰符等等。如果想在这些语境中使用变量,一定要把它们声明为constex转载 2021-05-16 10:57:43 · 225 阅读 · 0 评论 -
条款14.只要函数不会发生异常,就为其加上noexcept声明
只要函数不会发生异常,就为其加上noexcept声明在C++11中,无条件的noexcept就是为了不会发生异常的函数准备的。函数是否要加上如此声明,事关接口设计。当你命名知道一个函数不会发射异常却未给它加上noexcept声明的话,这就是接口规格缺陷。对不会发生异常的函数应用noexcept声明还有一个动机,就是可以让编译器生成更好的目标代码。int f(int x) throw(); //f不会发生异常,C++98风格int f(int x) noexcept; //f不会发生异常,C+转载 2021-05-16 10:57:03 · 467 阅读 · 0 评论 -
条款13.优先选用const_iterator,而非iterator
优先选择const_iterator,而非iteratorconst_iterator在STL中相当于指向const的指针。它们指向不可被修改的值,只有由可能就应该使用const。任何时候只要你需要一个迭代器而其指向的内容没有修改必要,就应该使用const_iterator。在C++11中,获取和使用const_iterator变得很容易,容器的成员函数cbegin()和cend()都返回const_iterator类型,甚至对于非const容器也是如此,并且STL成员函数若要取用指示位置的迭代器(例转载 2021-05-16 10:56:15 · 364 阅读 · 0 评论 -
条款12.为意在改写的函数添加override声明
为意在改写的函数添加override声明class Base{public: virtual void doWork(); //基类中的虚函数};class Derived : public Base{public: virtual void doWork(); //改写了Base::dowork};std::unique_ptr<Base> upb = std::make_unique<Derived>(); //创建基类指针,指向派生类对象转载 2021-05-15 12:24:43 · 192 阅读 · 0 评论 -
条款11.优先选择删除函数,而不是private未定义函数
优先选择删除函数,而非private未定义函数如果你写了代码给其他程序员用,并且你想阻止它们调用某个特定函数的话,那你只需不要声明该函数即可。函数未经声明,不可调用。但是有时候c++会替你声明函数,而如果你要阻止这些客户调用这些函数。这种情况仅仅发生在“特种成员函数”身上,即c++会在需要时自动生成的成员函数。C++98为了阻止这些函数被使用,采取的做法是将其声明为private,并且不去定义它们。在C++中,对输入流和输出流进行复制是不可取的。为了让输入流和输出流成为不可复制的。在C++98中的转载 2021-05-15 12:23:24 · 109 阅读 · 0 评论 -
条款10.优先使用限定作用域的枚举类型,而非不限定作用域的枚举类型
优先选定限定作用域的枚举类型,而非不限作用域的枚举类型先说一个通用规则,如果在一对大括号里面声明了一个名字,则该名字的可见性就被限定在括号括起来的作用域内,但这个规则不适用于c++98风格的枚举类型中定义的枚举量。这些枚举量的名字属于包含着这个枚举类型的作用域,这就意味着在此作用域中不能有其他实体取相同的名字;enum Color { black, white, red }; //black,white,red所在作用域和Color相同auto white = false; //错误,whi转载 2021-05-15 12:22:52 · 300 阅读 · 0 评论 -
条款9.优先选用别名声明,而非typedef
优先选择别名声明,而非typedeftypedef用法:typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;using用法(别名声明):using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;别名声明可以模板化(这种情况下,它们被称为别转载 2021-05-15 12:22:21 · 172 阅读 · 0 评论 -
条款8.优先选择nullptr,而非0或null
优先选择nullptr,而非0或NULL字面常量0的类型是int,而非指针。当c++在只能使用指针的语境中发现了一个0,它也会把它勉强解释为空指针。c++的基本观点还是0的类型是int,而非指针。从实际效果来说,以上结论对NULL也成立。NULL在技术细节上有一些不清不楚的成分,因为标准允许各个实现给予NULL非int的整数类型(如long)。0和NULL都不具备指针类型。c++98中,这样的基本观点可能在指针类型和整型之间进行重载时发生意外。void f(int);void f(bool);转载 2021-05-15 12:19:24 · 153 阅读 · 0 评论 -
条款7.在创建对象时注意区分()和{}
在创建对象时注意区分()和{}指定初始化的方式包括使用小括号,使用等号,或者使用大括号。int x(0); //初始化物在小括号内int y = 0; //初始化物在等号之后int z{ 0 }; //初始化物在大括号内//在很多情况下,使用一个等号和一对大括号也是可以的int z = { 0 };使用一个等号来书写初始化语句往往会让人意味这里面会发生一次赋值,但其实并没有。Widget w1; //调用默认构造函数Widget w2 = w1; //并非赋值,调用的是拷贝构造函数w转载 2021-05-15 12:16:57 · 261 阅读 · 0 评论 -
条款6.当auto推导的类型不符合要求时,使用带显式类型的初始化物习惯用法
当auto推导的类型不符合要求时,使用带显式类型的初始化物习惯用法举个例子,假设有一个函数接受一个Widget并返回一个std::vector<bool>,其中每一个bool元素都代表着Widget是否提供一种特定功能;Widget w;std::vector<bool> features(const Widget& w);//第5个比特代表的意思是:Widget是否具有高优先级bool highPriority = features(w)[5]; //w具有高转载 2021-05-15 12:15:57 · 157 阅读 · 0 评论 -
条款5.优先选择auto,而非显式类型声明
用auto声明的变量,它从初始化物来进行类型推导,所以它们必须初始化。int x1; //有潜在的未初始化风险auto x2; //编译错误!必须有初始化物auto x3 = 0; //没问题由于auto使用了类型推导,所以它只能表示只有编译器掌握的类型。auto derefUpLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2){ re转载 2021-05-15 12:15:26 · 164 阅读 · 0 评论 -
条款3.理解decltype
const int i = 0; //decltype(i)是const intbool f(const Widget& w); //decltype(w)是const Widget& //decltype(f)是bool(const Widget&)struct Point{ int x,y; //decltype(Point::x)是int}; //decltype(Point::y)是intWidget w; //decltyp转载 2021-05-15 12:14:44 · 124 阅读 · 0 评论 -
条款2.理解auto类型推导
条款1中,模板类型推导的函数模板形如template<typename T>void f(ParamType param);f(expr); //以某表达式调用f在f的调用语句中,编译器会利用expr来推导T和ParamType的类型。当某变量采用auto来声明时,auto就扮演了模板中的T这个角色,而变量的修饰词则扮演的是ParamType的角色。auto x = 27; //x的类型饰词就是auto自身const auto cx = x; //x的类型饰词是const au转载 2021-05-15 12:13:29 · 274 阅读 · 0 评论 -
条款1.类型推导
理解模板类型推导函数模板大致形如template<typename T>void f(ParamType param);而一次调用形如f(expr);在编译期,编译器会通过expr推导两个类型:一个是T的类型,另一个是ParamType的类型,这两个类型往往不一样,因为,ParamType常会包含一些修饰词,如const或引用符号等限定词。例如,若模板声明如下:template<typename T>void f(const T& param); //P转载 2021-05-14 21:00:23 · 249 阅读 · 0 评论