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、

  • 相关阅读:
    BZOJ2219数论之神——BSGS+中国剩余定理+原根与指标+欧拉定理+exgcd
    Luogu 3690 Link Cut Tree
    CF1009F Dominant Indices
    CF600E Lomsat gelral
    bzoj 4303 数列
    CF1114F Please, another Queries on Array?
    CF1114B Yet Another Array Partitioning Task
    bzoj 1858 序列操作
    bzoj 4852 炸弹攻击
    bzoj 3564 信号增幅仪
  • 原文地址:https://www.cnblogs.com/tekkaman/p/10211551.html
Copyright © 2011-2022 走看看