C++0x 走马观花:核心语言运行时性能增强

3 核心语言运行时性能增强

  这些语言特征存在的目的是为各种性能的提升提供支持,包括内存或者速度。

3.1 右值引用和move语法
  在标准C++里面,临时变量(被称为右值,因为它们处于一个赋值语句的右边)可以被传递给函数,但是它们只能作为const&类型被接受。因此,对于一个函数而言是无法从形如const&类型的参数中区分出右值引用和一个常规的对象。而且由于类型是const&,也就无法改变这个对象。

  C++0x增强了一个新的引用类型,被称作右值引用,定义为typename &&。这些可以作为非const值被接受,从而允许一个对象去修改它们。这种修改就允许了一些对象来创造move语法。

  例如,std::vector,在内部是通过对C风格的带有长度信息的数组进行封装来实现的。如果一个vector临时变量需要被创建或者从函数返回的时候,它只能是创建一个新的vector,然后将所有的右值数据对其进行copy赋值。之后这个临时变量会被销毁,删除所有的数据。

  通过右值引用,std::vector可以提供一个move构建函数,它使用对vector的右值引用作为参数,能够简单地将指向内部C风格数组的指针到新的vector里面,然后设置右值为一个空的状态。这里面就没有数组的拷贝,并且析构空的了临时变量也不会销毁那部分内存。这个返回临时vector的函数仅仅需要返回std::vector<>&&即可。如果vector没有move语法,那么拷贝构建函数就会被调用,如同以往的const std::vector<>&一样。如果move构建函数存在,那么它就会被调用,而重要的内存分配就会被避免。

  出于安全的原因,一个有名字的变量将永远不会被作为右值,即便它们被按照右值来声明。为了获得一个右值,可以使用库函数std::move()。

    bool is_r_value(int &&) { return true; }
    bool is_r_value(const int &) { return false; }
    
    void test(int && i)
    {
        is_r_value(i); // false
        is_r_value(std::move(i)); // true
    }

  由于右值引用这个名词的性质以及对左值引用(常规的引用)这个名词的一些修改,右值引用允许开发人员提供优美的函数前向传递操作(perfect forwarding)。当与变参模板一起工作的时候,这个能力使得函数模板成为可能,它可以将参数前向传递给另外一个持有特定参数的函数。对于前向构建函数的参数这非常有用,可以创建工厂函数,使其能够自动调用合适的使用特定参数的构建函数。

3.2 通用的常量表达式
  C++一直有常量表达式的概念。比如表达式3+4可以永远得到相同的结果,而没有任何副作用。对于编译器来说,常量表达式是进行优化的时机,编译器可以在编译时执行它,并将结果放在程序之中。而且,在C++规范里面有很多地方要求使用常量表达式。定义一个数组需要常量表达式,而枚举变量必须是常量表达式。

  然而,一旦遇到了函数调用或者对象构建的时候,常量表达式就常常无能为力了。所以下面这个简单的例子是不合法的:

    int GetFive() {return 5;}
    
    int some_value[GetFive() + 5]; //create an array of 10 integers. illegal C++

  这不是合法的C++语句,因为GetFive() + 5不是一个常量表达式。虽然GetFive实际上在运行时是不变的,编译起却没有办法知道这一点。理论上,函数可以影响全局变量,或者调用其他非运行时常量的函数等等。

  C++0x引入了关键字constexpr,它允许用户来确保某个函数或者对象构建在编译时是不变的。上面的例子就可以改写成以下的形式:

    constexpr int GetFive() {return 5;}
    
    int some_value[GetFive() + 5]; //create an array of 10 integers. legal C++0x

  这使得编译器可以去理解/验证GetFive是编译时常量。

  在函数上使用constexpr修饰对函数可以做什么施加了非常严格的限制。首先,函数必须不能是void类型;其次,函数内容必须是"return expr"这种形式;第三,expr必须是常量表达式,或者它可以使用其它的常量表达式变量;第四,禁止所有类型的常量表达式中的递归;最后,这种函数直到被定义之后才能调用。

  变量也可以被定义为常量表达式值:

    constexpr double forceOfGravity = 9.8;
    constexpr double moonGravity = forceOfGravity / 6;

  常量表达式数据变量隐含地就是const。它们只能存放常量表达式或者常量表达式构建的结果。

  为了通过用户定义类型来创建常量表达式变量,构建函数也可以被声明为constexpr。一个常量表达式构建函数和常量表达式普通函数一样,必须先定义再使用。它的函数体必须是空。它的成员也必须被初始化为常量表达式。这种类型的析构函数和一般形式一样。

  为了允许从constexpr函数里面按值返回,拷贝constexpr构建类型的动作也应该被定义为constexpr。一个类的所有成员函数,比如拷贝构建函数,运算符重载等,只要它们满足函数常量表达式的定义要求,也可以被声明为constexpr。这可以让编译器在编译的时候就能够进行类的复制,并执行一些运算等等。

  一个常量表达式普通函数或者构建函数可以使用非constexpr的参数。就好比,一个constexpr整型可以赋值给一个非constexpr变量,constexpr函数也可以使用非constexpr的参数,其结果被放在非constexpr变量里。当表达式所有的成员都是constexpr的时候,这个关键字就使编译时间恒定成为可能。

3.3 Plan Old Data(POD)定义的修改
  在标准C++里面,struct必须符合一系列的规则才能被称为POD类型。需要很多类型去满足定义是有充分理由的。符合这种定义的类型允许最终的实现结果可以产生兼容于C的对象布局。然而,C++03里面的规则列表还是过于严格了。

  C++0x要放松关于POD定义的几个规则。

  如果class/struct是传统的,标准布局的,并且它所有的非静态成员都是POD,那么它就被认为是POD。一个传统的class或者struct被定义为:

    1. 有传统的缺省构建函数。可以使用缺省构建函数语法(SomeConstructor() = default;);
    2. 有传统的拷贝构建函数,可以是缺省的形式;
    3. 有传统的拷贝赋值操作符,可以是缺省的形式;
    4. 有传统的析构函数,不能是虚函数;

  一个标准布局的class或者struct被定义为为:

   1. 只有非静态数据成员,而且成员也是标准布局类型;
   2. 对于所有非静态成员,拥有相同的存取控制(public, private, protected);
   3. 没有虚函数;
   4. 没有虚基类;
   5. 只有标准布局类型的基类;
   6. 没有和第一个定义的非静态成员类型一样的基类;
   7. 或者没有基类有非静态成员,或者在大多数派生类里面没有非静态成员,最多只有一个基类里面有非静态成员。根本上,在类的层次关系上只能有一个类可以有非静态成员。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值