zoukankan      html  css  js  c++  java
  • 深入理解C++11【2】

    深入理解C++11【2】

    1、继承构造函数。

      当基类拥有多个构造函数的时候,子类不得不一一实现。

      C++98 可以使用 using 来使用基类的成员函数。

    #include < iostream> using namespace std; 
    struct Base { 
        void f( double i){ 
            cout << "Base:" << i << endl; 
        } 
    }; 
    struct Derived : Base { 
        using Base:: f; 
        void f( int i) { 
            cout << "Derived:" << i << endl; 
        } 
    }; 
    
    int main() { 
        Base b; 
        b. f( 4. 5); // Base: 4. 5 
        
        Derived d; 
        d. f( 4. 5); // Base: 4. 5 
    } // 编译 选项: g++ 3- 1- 3. cpp

      C++11中,这个功能由成员函数扩展到了构造函数上。

    struct A { 
        A( int i) {} 
        A( double d, int i) {} 
        A( float f, int i, const char* c) {} // ... 
    }; 
    
    struct B : A { 
        using A:: A; // 继承 构造 函数 
        // ... 
        virtual void ExtraInterface(){} 
    };

      这 意味着 如果 一个 继承 构造 函数 不被 相关 代码 使用, 编译器 不 会为 其 产生 真正 的 函数 代码。

      对于 继承 构造 函数 来讲, 参数 的 默认值不会被继承 的。 事实上, 默认值 会 导致 基 类 产生 多个 构造 函数 的 版本, 这些 函数 版本 都会 被 派生 类 继承。

      如果 一旦 使用 了 继承 构造 函数, 编译器 就不 会 再为 派生 类 生成 默认 构造 函数 了,

    struct A { A (int){} }; 
    struct B : A{ using A:: A; }; 
    B b; // B 没有 默认 构造 函数

      截至2013年,还没有编译器实现了继承构造函数。

    2、委派构造函数。

      C++98 中编译器 不允许 在 构造 函数 中 调用 构造 函数, 即使 参数 看起来 并不 相同。以下代码会产生编译错误。

    Info() { InitRest(); } 
    Info( int i) { this-> Info(); type = i; }
    Info( char e) { this-> Info(); name = e; }

      所以C++98的hacker喜欢使用 placement new 来实现调用其它构造函数。

    Info() { InitRest(); } 
    Info( int i) { new (this) Info(); type = i; } 
    Info( char e) { new (this) Info(); name = e; }

      C++11 中可以使用委派构造函数来简化代码。

    class Info { 
        public: 
            Info() { InitRest(); } 
            Info( int i) : Info() { type = i; } 
            Info( char e): Info() { name = e; } 
        private: 
            void InitRest() { /* 其他 初始化 */ } 
            int type {1}; 
            char name {'a'}; // ... 
    }; // 编译 选项: g++ -c -std= c++ 11 3- 2- 3. cpp

      调用“ 基准 版本” 的 构造 函数 为 委派 构造 函数( delegating constructor), 而被 调用 的“ 基准 版本” 则为 目标 构造 函数( target constructor)。

      构造 函数 不能 同时“ 委派” 和 使用 初始化 列表, 所以 如果 委派 构造 函数 要给 变量 赋 初值, 初始化 代码 必须 放在 函数 体中。如下:

    struct Rule1 { 
        int i; 
        Rule1( int a): i( a) {} 
        Rule1(): Rule1( 40), i( 1) {} // 无法 通过 编译 
    };

       目标 构造 函数 的 执行 总是 先于 委派 构造 函数 而 造成 的。

       委派 构造 的 一个 很 实际 的 应用 就是 使用 构造模板函数 产生 目标 构造 函数,

    class TDConstructed 
    {
            template< class T> 
            TDConstructed( T first, T last) : l( first, last) {} 
            list< int> l; 
        public: 
            TDConstructed( vector< short> & v): TDConstructed( v. begin(), v. end()) {} 
            TDConstructed( deque< int> & d): TDConstructed( d. begin(), d. end()) {} 
    }; // 编译 选项: g++ -c -std= c++ 11 3- 2- 6. cpp

       委托构造函数的异常捕获稍稍有些另类,try-catch 是在函数外:

    class DCExcept { 
        public: 
            DCExcept( double d) try : DCExcept( 1, d) { 
                cout << "Run the body." << endl; // 其他 初始化 
            } 
            catch(...) { 
                cout << "caught exception." << endl; 
            }

    3、移动语义

      C++98 中的经典问题,会产生多达分别3次的construct、destruct:

    HasPtrMem GetTemp() { 
        return HasPtrMem(); 
    } 
    int main() { 
        HasPtrMem a = GetTemp(); 
    } 
    // 编译 选项: g++ 3- 3- 3. cpp -fno- elide- constructors

      输出如下:

    Construct: 1 
    Copy construct: 1 
    Destruct: 1 
    Copy construct: 2 
    Destruct: 2 
    Destruct: 3

      

       移动构造函数与 Copy构造函数:

    HasPtrMem( const HasPtrMem & h): d( new int(* h. d)) 
    { 
        cout << "Copy construct: " << ++ n_ cptr << endl; 
    } 
    
    HasPtrMem( HasPtrMem && h): d( h. d) 
    { 
        // 移动 构造 函数 
        h. d = nullptr; // 将临 时值 的 指针 成员 置 空 
        cout << "Move construct: " << ++ n_ mvtr << endl; 
    }
    Construct: 1 Resource from GetTemp: 0x603010 
    Move construct: 1 
    Destruct: 1 
    Move construct: 2 
    Destruct: 2 
    Resource from main: 0x603010 
    Destruct: 3

    4、左值、右值、右值引用

      1)等号左边的是“左值”。等号右边的是“右值”。

      2)可以取地址的、有名字是左值。不能取地址、没有名字的就是右值。

      C++11程序中,所有值必属于左值、将亡值、右值。

      右值引用是不能绑定到任何左值的,如下代码就无法通过编译:

    int c; 
    int && d = c;

      常量左值引用可以接受右值,同时也像右值引用一样将右值生命周期延长。如下第一行:

    const bool & judgement = true;
    const bool judgement = true;

      C++98中也常可以使用常量左值引用来减少临时对象的开销。

    #include < iostream> 
    using namespace std; 
    struct Copyable { 
        Copyable() {} 
        Copyable( const Copyable &o) { 
            cout << "Copied" << endl; 
        } 
    }; 
    
    Copyable ReturnRvalue() { return Copyable(); } 
    void AcceptVal( Copyable) {} 
    void AcceptRef( const Copyable & ) {}
    
    
    int main() 
    { 
        cout << "Pass by value: " << endl; 
        AcceptVal( ReturnRvalue()); // 临 时值 被 拷贝 传入 
        
        cout << "Pass by reference: " << endl; 
        AcceptRef( ReturnRvalue()); // 临 时值 被 作为 引用 传递 
    } // 编译 选项: g++ 3- 3- 5. cpp -fno- elide- constructors
    Pass by value: 
    Copied 
    Copied 
    Pass by reference: 
    Copied

       std::move 的作用是强制一个左值成为右值。

      因为 const T& 是万能引用,可以指向右值。所以当类实例无 MoveConstructor时,将会调用 CopyConstructor来替代。

      但 const T&& 常量右值引用没有卵用,一来MoveConstructor需要引用右值;二来如果右值不能改,使用 const T& 就可以了。所以 const T&&没有卵用。

      

      为了知道一个类型是否是引用类型,可以使用 <type_traits>头文件中的3个模板类:

      1)is_rvalue_reference

      2)is_lvalue_reference

      3)is_reference

    5、std::move

      std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用。std::move 基本赞同于一个类型转换:

    static_ cast< T&&>( lvalue);

      使用 std::move 时,我们希望转换为右值引用的这个值是一个生命期即将结束的对象。一个正确的 std::move 的使用场景。

    class HugeMem{ 
        public: 
            HugeMem( int size): sz( size > 0 ? size : 1) { c = new int[ sz]; } 
            ~ HugeMem() { delete [] c; } 
            
            HugeMem( HugeMem && hm): sz( hm. sz), c( hm. c) { hm. c = nullptr; }
            int * c; 
            int sz; 
    }; 
    
    class Moveable{ 
        public: 
            Moveable(): i( new int( 3)), h( 1024) {} 
            ~ Moveable() { delete i; } 
            
            Moveable( Moveable && m): i( m. i), h( move( m. h)) { // 强制 转为 右 值, 以 调用 移动 构造 函数 
                m. i = nullptr; 
            } 
            int* i; 
            HugeMem h; 
    };

    6、移动语义的其它问题。

      下述代码会使得 的 临时 变量 常 量化, 成为 一个 常量 右 值, 那么 临时 变量 的 引用 也就 无法 修改, 从而 导致 无法 实现 移动 语义。

    Moveable( const Moveable &&)
    const Moveable ReturnVal();

      编译器 会为 程序员 隐式 地 生成 一个( 隐式 表示 如果 不被 使用 则 不 生成) 移动 构造 函数。 不过 如果 程序员 声明 了 自定义 的 拷贝 构造 函数、 拷贝 赋值 函数、 移动 赋值 函数、 析 构 函数 中的 一个 或者 多个, 编译器 都不 会 再为 程序员 生成 默认 版本。

      声明 了 移动 构造 函数、 移动 赋值 函数、 拷贝 赋值 函数 和 析 构 函数 中的 一个 或者 多个, 编译器 也不 会 再为 程序员 生成 默认 的 拷贝 构造 函数。 所以 在 C++ 11 中, 拷贝 构造/ 赋值 和 移动 构造/ 赋值 函数 必须 同时 提供, 或者 同时 不 提供, 程序员 才能 保证 类同 时 具有 拷贝 和 移动 语义。

       只有 移动 语义 的 类型 则 非常 有趣, 因为 只有 移动 语义 表明 该 类型 的 变量 所 拥有 的 资源 只能 被 移动, 而 不能 被 拷贝。 那么 这样 的 资源 必须 是 唯一 的。 因此, 只有 移动 语义 构造 的 类型 往往 都是“ 资源 型” 的 类型, 比如说 智能 指针, 文件 流 等,

       有了移动语义,可以实现高性能的 swap 函数。

    template < class T> 
    void swap( T& a, T& b) 
    { 
        T tmp( move( a)); 
        a = move( b); 
        b = move( tmp); 
    }

      程序员 应该 尽量 编写 不 抛出 异常 的 移动 构造 函数, 通过 为 其 添加 一个 noexcept 关键字, 可以 保证 移动 构造 函数 中 抛出 来的 异常 会 直接 调用 terminate 程序 终止 运行, 而 不是 造成 指针 悬挂 的 状态。

      一个 std:: move_ if_ noexcept 的 模板 函数 替代 move 函数。 该 函数 在 类 的 移动 构造 函数 没有 noexcept 关键字 修饰 时 返回 一个 左 值 引用 从而 使 变量 可以 使用 拷贝 语义, 而在 类 的 移动 构造 函数 有 noexcept 关键字 时, 返回 一个 右 值 引用, 从而 使 变量 可以 使用 移动 语义。如下:

      

    struct Maythrow 
    { 
        Maythrow() {} Maythrow( const Maythrow&) { 
            std:: cout << "Maythorow copy constructor." << endl; 
        } 
        
        Maythrow( Maythrow&&) { 
            std:: cout << "Maythorow move constructor." << endl; 
        } 
    }; 
    
    struct Nothrow 
    { 
        Nothrow() {} 
        Nothrow( Nothrow&&) noexcept { 
            std:: cout << "Nothorow move constructor." << endl; 
        }
        
        Nothrow( const Nothrow&) { 
            std:: cout << "Nothorow move constructor." << endl; 
        } 
    }; 
    
    int main() 
    { 
        Maythrow m; 
        Nothrow n; 
        
        Maythrow mt = move_ if_ noexcept( m); // Maythorow copy constructor. 
        Nothrow nt = move_ if_ noexcept( n); // Nothorow move constructor. 
        return 0; 
    } // 编译 选项: g++ -std= c++ 11 3- 3- 8. cpp

      在 本节 中 大量 的 代码 都 使用 了- fno- elide- constructors 选项 在 g++/ clang++ 中 关闭 这个 优化。如果关闭,则下述代码只会有一次 constructor、destructor:

    A ReturnRvalue() { A a(); return a; } 
    A b = ReturnRvalue();

    7、完美转发

      完美 转发( perfect forwarding), 是指 在 函数 模板 中, 完全 依照 模板 的 参数 的 类型, 将 参数 传递 给 函数 模板 中 调用 的 另外 一个 函数。下面的例子中,如果传入的是一个右值,t被传给内部函数时成了一个左值,不是完美转发。

    template < typename T> 
    void IamForwording( T t) 
    { 
        IrunCodeActually( t); 
    }

      我们希望传入左值,传出也是左值;传入右值,传出也是右值。

      C++11加入了引用折叠新规则。在C++98中,以下代码会导致编译错误。

    typedef const int T; 
    typedef T& TR; 
    TR& v = 1;     // 该 声明 在 C++ 98 中会 导致 编译 错误

      

      

      模板类型 的 推导 规则 就比 较 简单, 当 转发 函数 的 实 参 是 类型 X 的 一个 左 值 引用, 那么 模板 参数 被 推导 为 X& 类型, 而 转发 函数 的 实 参 是 类型 X 的 一个 右 值 引 用的 话, 那么 模板 的 参数 被 推导 为 X&& 类型。

    template < typename T> 
    void IamForwording( T && t) { 
        IrunCodeActually( static_ cast< T &&>(t)); 
    }

      上面就是完美转发。

      

    8、显式类型转换

      只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。

    class String
    {
    public:
        String ( const char* p ); // 用C风格的字符串p作为初始化值
        //
    }
     
    String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(”hello”)

      在 C++ 11 中, 标准 将 explicit 的 使用范围 扩展到 了 自定义 的 类型 转换 操作 符 上。

    class ConvertTo {}; 
    
    class Convertable 
    { 
        public: 
            explicit operator ConvertTo () const { 
                return ConvertTo(); 
            } 
    }; 
            
    void Func( ConvertTo ct) {} 
    void test() 
    { 
        Convertable c; 
        ConvertTo ct( c); // 直接 初始化, 通过 
        ConvertTo ct2 = c; // 拷贝 构造 初始化, 编译 失败 
        ConvertTo ct3 = static_ cast< ConvertTo>( c); // 强制 转化, 通过 
        Func( c); // 拷贝 构造 初始化, 编译 失败 
    } 
    // 编译 选项: g++ -std= c++ 11 3- 4- 3. cpp

    9、初始化列表

      C++98 中只允许对数组进行列表初始化,C++11扩展到了所有元素。

    #include < vector> 
    #include < map> 
    using namespace std; 
    int a[] = {1, 3, 5}; // C++ 98 通过, C++ 11 通过 
    int b[] {2, 4, 6}; // C++ 98 失败, C++ 11 通过 
    vector< int> c{ 1, 3, 5}; // C++ 98 失败, C++ 11 通过 
    map< int, float> d = {{1, 1. 0f}, {2, 2. 0f} , {5, 3. 2f}}; // C++ 98 失败, C++ 11 通过 
    // 编译 选项: g++ -c -std= c++ 11 3- 5- 1. cpp

      如上,列表 初始化 可以 在“{}” 花 括号 之前 使用 等号, 其 效果 与 不带 使用 等号 的 初始化 相同。

       {}和()一样,也可以用于 new操作符中。

    int * i = new int( 1);
    double * d = new double{ 1. 2f};

       自定义类型如需要初始化列表功能,需要提供一个构造函数,此函数使用唯一一个参数,initialize_list。

    enum Gender {boy, girl}; 
    
    class People { 
        public: 
            People( initializer_ list< pair< string, Gender>> l) 
            { 
                // initializer_ list 的 构造 函数 
                auto i = l. begin(); 
                for (;i != l. end(); ++ i) 
                    data. push_ back(* i); 
            } 
            
        private: 
            vector< pair< string, Gender>> data; 
    }; 
    
    People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}}; // 编译 选项: g++ -c -std= c++ 11 3- 5- 2. cpp

       函数也可以使用初始化列表:

    void Fun( initializer_ list< int> iv){ } 
    
    int main() { 
        Fun({ 1, 2}); 
        Fun({}); // 空 列表 
    } // 编译 选项: g++ -std= c++ 11 3- 5- 3. cpp

      当初始化列表 + operator[] + operator= 时,会产生强大的语法效果:

    using namespace std; 
    
    class Mydata 
    { 
        public: 
            Mydata & operator [] (initializer_ list< int> l) 
            { 
                for (auto i = l. begin(); i != l. end(); ++ i) 
                    idx. push_ back(* i); 
                return *this; 
            } 
            
            Mydata & operator = (int v) 
            { 
                if (idx. empty() != true) 
                { 
                    for (auto i = idx. begin(); i != idx. end(); ++ i) 
                    { 
                        d. resize((* i > d. size()) ? *i : d. size()); 
                        d[* i - 1] = v; 
                    } 
                    
                    idx. clear(); 
                } 
                return *this; 
            } 
            
            void Print() 
            { 
                for (auto i = d. begin(); i != d. end(); ++ i) 
                    cout << *i << " "; cout << endl; 
            } 
        
        private: 
            vector< int> idx; // 辅助 数组, 用于 记录 index 
            vector< int> d; 
    }; 
    
    int main() 
    { 
        Mydata d; 
        d[{ 2, 3, 5}] = 7; 
        d[{ 1, 4, 5, 8}] = 4; 
        d. Print(); // 4 7 7 4 4 0 0 4 
    } 
    // 编译 选项: g++ -std= c++ 11 3- 5- 4. cpp
    View Code

       

      初始化 列表 还可以 用于 函数 返回 的 情况。 返回 一个 初始化 列表, 通常 会 导致 构造 一个 临时 变量,

    vector< int> Func() { return {1, 3}; }

    10、类型收窄

      在 C++ 11 中, 使用 初始化 列表 进行 初始化 的 数据 编译器 是 会 检查 其是 否 发生 类型 收 窄 的。

    const int x = 1024; 
    const int y = 10; 
    char a = x; // 收 窄, 但可以 通过 编译 
    char* b = new char( 1024); // 收 窄, 但可以 通过 编译 
    char c = {x}; // 收 窄, 无法 通过 编译 
    char d = {y}; // 可以 通过 编译 
    unsigned char e {-1}; // 收 窄, 无法 通过 编译 
    float f { 7 }; // 可以 通过 编译 
    int g { 2. 0f }; // 收 窄, 无法 通过 编译 
    float * h = new float{ 1e48}; // 收 窄, 无法 通过 编译 
    float i = 1. 2l; // 可以 通过 编译 
    // 编译 选项: clang++ -std= c++ 11 3- 5- 5. cpp

       在 C++ 11 中, 列表 初始化 是 唯一 一种 可以 防止 类型 收 窄 的 初始化 方式。 这也 是 列表 初始化 区别于 其他 初始化 方式 的 地方。

    11、

    12、

    13、

  • 相关阅读:
    事件冒泡
    jquery validation验证身份证号、护照、电话号码、email
    移动平台对 meta 标签的定义
    css3属性笔记
    渐变的参数
    各浏览器前缀
    Ubuntu20.04安装Matlab2018b
    win7 php安装使用
    mysql输入命令后没响应
    CentOS7上搭建Dokuwiki
  • 原文地址:https://www.cnblogs.com/tekkaman/p/10211551.html
Copyright © 2011-2022 走看看