zoukankan      html  css  js  c++  java
  • 《Effective Modern C++》

    下面面四个表达式中前两个实际上是声明一个值为27int类型的变量。而后两个,是声明一个类型为std::initializer_list<int>,并且具有一个值为27的元素!

    auto x1 = 27;        //type is int, value is 27
    auto x2(27);         //同上
    auto x3 = {27};      //type is std::initializer_list<int>, value is {27}
    auto x4{27};         //同上

    这是因为auto类型推导有特俗的规则。当为auto声明的类型进行初始化使用封闭的大括号时,则推导的类型为std::initializer_list

    C++14允许使用auto去推导函数的返回值(参见条款3),并且C++14中的lambda表达式在参数声明时可以使用auto类型推导。但是,这里使用的auto推导采用的是模板推导的规则,而不是auto类型推导的规则。

    auto可以推导封闭大括号类型为std::initializer_list,但是template不可以。

     

    template<typename Container, typename Index> //C++11的 
    auto                                         // 的最终 
    authAndAccess(Container&& c, Index i)        // 版本
    
    -> decltype(std::forward< Container>(c)[i]) 
    { 
    authenticateUser(); 
    return std::forward<Container>(c)[i]; 
    }
    template<typename Container, typename Index> //C++14的 
    decltype(auto)                               // 最终 
    authAndAccess(Container&& c, Index i)        // 版本 
    { 
    authenticateUser(); 
    return std::forward<Container>(c)[i]; 
    }
     

     

    std::cout << typeid(x).name() << '
    '; // 显示x和y的 
    std::cout << typeid(y).name() << '
    '; // 类型
    这个方法依赖于typeid作用于一个对象上时,返回类型为std::type_info这一个事实,type_info有一个叫name的成员函数,提供了一个C风格的字符串(例如 const char*)来表示这个类型的名字
    template<typename T> 
    void f(const T& param) 
    { 
    using std::cout; 
    cout << "T = " << typeid(T).name() << '
    '; // 显示T的类型 
    cout << "param = " << typeid(param).name() << '
    '; // 显示参数Param的类型  
    }
    GNU和Clang的执行结果是下面这样:
    
    T = PK6Widget 
    param = PK6Widget
    
    我们已经知道PK意味着pointer to const,而6代表了类的名字中有多少个字母(Widget),所以这两个编译器告诉了我们T和param的类型都是const Widget*
    
    Morcrosoft的编译器提供了下面的结果
    
    T = class Widget const * 
    param = class Widget const *
    
    这三个编译器都提供了一样的信息,这或许暗示了结果应该是准确的,但是让我们看的更细致一点,在模板f中,param的类型被声明为constT&,既然如此的话,param和T的类型一样难道不让人感到奇怪吗,如果T的类型是int,param的类型应该是const int&,看,一点都不一样。

     

     

    widget(int i,bool b);
    widget(int i ,double d);
    widget(std::initializer_list<bool> il);

     

    widget w{10,5.0}

     

    {}太厉害,即使上面的有更好的初始化函数匹配,还是会直接尝试调用第三个函数,将10,5.0转换为bool(narrow conversion),但是大括号初始化禁止narrow conversion,报错。

    空大括号表示没有 参数,而不是一个空的std::initializer_list列表。

    widget w5{{}};//这样才能以空列表为参数调用相应构造函数

     

    条款8:prefer nullptr to 0 and NULL

     0和NULL可能被推导成 int或int-like。避免函数重载时0和NULL被推导成 int或int-like。

    nullptr可以隐式转换为任何指针类型

    尤其涉及到模板的时候。

     

    条款10:prefer scoped enums to unscoped enums

    C++98的的enum是unscoped的。新的scoped enums写法:enum class Status;

     scoped enums 可以避免命名空间污染。

    支持前向声明,从而减少依赖关系,不必全部重新编译。

    为什么不需要重新编译呢,因为这种枚举类型有一个默认类型(int),因此在编译时可以确定。当然类型可以修改:

    enum class Status: std::unint32_t

     

     条款11:prefer deleted functions to private undefined ones

    编译器就可以检测到错误而不是链接期。delete成员函数设置为public,这样调用错误可以得到更准确的报错。

    任何函数都可以设置为deleted。可以重载函数并设置为deleted阻止错误类型参数调用。

    还可以将某些函数模板的特化设置为deleted,阻止以这些类型为参数调用该函数。

     

     条款16:make const member functions  thread safe

     简单的互斥计算,std::atomic可能是更好的选择(可能开销更小):

    定义:
    mutable std::atomic<unsigned> callCount{0};
    ……
    用法:
    ++callCount;

    std::mutex、std::atomic等不能复制或移动,因此以它们为成员的类也不能复制或移动。

     

     条款18:use std::unique_ptr for exclusive-ownership resource management

    auto delInvmt = [](Investment* pInvestment) // custom
    {                                                                     // deleter
        makeLogEntry(pInvestment);                    // (a lambda
        delete pInvestment;                                   // expression)
    };
    
    template<typename... Ts> // revised
    std::unique_ptr<Investment, decltype(delInvmt)> // return type
    makeInvestment(Ts&&... params)
    {
        std::unique_ptr<Investment, decltype(delInvmt)> // ptr to be
           pInv(nullptr, delInvmt);                                       // returned
    
        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::unique_ptr的第二个参数delInvmt,是一个定制的删除器。另外注意它的reset()函数用法。

    1.std::unique_ptr是一个小巧,迅速,仅能移动(move-only)的灵巧指针,它通过独享所有权的语义来管理资源。
    2.默认情况下,资源释放是通过delete来析构,但是定制删除器可以被用来指定。有状态的删除器和函数指针删除器会增加std::unique_ptr对象的尺寸大小。
    3.std::unique_ptr很容易转换成std::shared_ptr

    c++11 条款19:使用std::shared_ptr来进行共享所有权的资源管理

    auto loggingDel = [](Widget *pw)                   // custom deleter
                                 {                                        // (as in Item 18)
                                     makeLogEntry(pw);
                                     delete pw;
                                 } ;
    std::unique_ptr<                                 // deleter type is
        Widget, decltype(loggingDel)         // part of ptr type
        > upw(new Widget, loggingDel);
    
    
    std::shared_ptr<Widget>                   // deleter type is not
        spw(new Widget, loggingDel);       // part of ptr type

    删除器是std::unique_ptr类型的一部分,但不是std::shared_ptr的一部分。

    另一个和std::unique_ptr不同的地方是,指定一个定制删除器并不会改变std::shared_ptr指针的大小。无论删除器怎样,一个std::shared_ptr内部包含的一定是两个指针。

    一个对象的控制块是由创建第一个指向该对象的std::shared_ptr指针的函数来建立的。至少这是我们预料中的。一般不可能在一个函数创建指向某对象的std::shared_ptr时会知道是否有其他的std::shared_ptr已经指向该对象,所以会用到下面的关于控制块创建的规矩:

    1.std::make_shared(见条款21)总是会创建控制块。该函数创建了一个新对象并指向它,所以当std::make_shared被调用时,当然没有该对象的控制块存在。

    2.当一个std::share_ptr是从一个独享所有权的指针(比如std::unique_ptr或者std::auto_ptr)创建时,会创建控制块。独享所有权的指针不使用控制块,因此被指向的对象也不会有控制块。(作为构建的一部分,这个std::shared_ptr会认为拥有了被指对象的所有权,因此,独享所有权的指针会被置空)。

    3.当std::shared_ptr是通过一个原始指针构造时,它会创建控制块。假如你想通过一个已有控制块的的对象去构造一个std::shared_ptr,你最好传一个std::shared_ptr或者std::weak_ptr(见条款20)作为构造函数的参数,而不是原始指针。std::shared_ptr的构造函数接受std::shared_ptr或者std::weak_ptr作为参数时,并不创建新的控制块,因为它依赖传递进来的灵巧指针已经指向的控制块。

    std::shared_ptr的API提供了这种情况的解决办法。它包含了一个可能在c++标准库里最奇怪的名字:std::enable_shared_from_this。

    它是个基类模板,你如果想一个被std::shared_ptr管理的类能够安全的从this指针来创建成std::shared_ptr,那你可以继承它,Widget继承std::enable_shared_from_this如下:

    class Widget: public std::enable_shared_from_this<Widget> {
    public:
        …
        void process();
        …
    }; 
    
    void Widget::process()
    {
        // as before, process the Widget
    // add std::shared_ptr to current object to processedWidgets
        processedWidgets.emplace_back(shared_from_this());
    }

    在内部,shared_from_this会查找当前对象的的控制块,并且创建一个新的std::share_ptr关联到控制块上。这个设计要依赖于当前对象已经有了一个相应的控制块。

    为此,必须已经存在一个指向当前对象的std::shared_ptr(比如在调用过shared_from_this成员函数之外已经有了一个)。

    假如没有这样一个std::shared_ptr存在(假如当前对象没有相关的控制块存在),那么调用行为将是未知的,尽管 shared_from_this通常会抛异常。

    上面的代码用到了The Curiously Recurring Template Pattern (CRTP) ,奇特的递归基类模板,

    1.std::shared_ptr提供了对共享所有权的任意资源的生命周期的很方便的管理。

    2.和std::unique_ptr相比,std::shared_ptr尺寸增加了一倍,同时因为控制块增加了负担,并且需要原子操作的引用计数。

    3.默认的资源释放是通过delete,但是也支持定制删除器。删除器的类型对std::shared_ptr本身的类型无影响。

    4.避免从原始指针变量去构造std::shared_ptr。

     

     

     

     

     

     

  • 相关阅读:
    从xib初始化的UIView如何继承?
    no implicit conversion of nil into String
    @synchronized(self) 加锁引起的Crash
    iOS手工Crash解析
    iOS线程While-True死循环会发生什么
    2019年新年总结
    218. The Skyline Problem-Hard
    ReplayKit 启动录制按钮 RPSystemBroadcastPickerView 的使用
    S212-搜索+字典树-212. Word Search II-(Hard)
    mybatis批量生成
  • 原文地址:https://www.cnblogs.com/ph829/p/6958812.html
Copyright © 2011-2022 走看看