C++11—More Effective

这里着重说说C++11中的更高效率的一些知识。

1、assert、static_assert(提前判误)

assert又叫断言,C语言中就有。想必也是听说过。在C++11中,使用assert要包含头文件#include <assert.h>,assert主要用来调试,正式的程序中很少使用。
assert 是运行期断言,它用来发现运行期间的错误,不能提前到编译期发现错误,也不具有强制性,也谈不上改善编译信息的可读性,既然是运行期检查,对性能当然是有影响的,所以经常在发行版本中,assert 都会被关掉。assert 的关键在于判断expression 的逻辑真假,如果为false,就会在stderr 上面打印一条包含"表达式,文件名,行号"的错误信息,然后调用abort 结束整个程序。

static_assert 这个关键字,用来做编译期间的断言,因此叫做静态断言。其语法很简单:static_assert(常量表达式,提示字符串)。如果第一个参数常量表达式的值为真(true 或者非零值),那么static_assert 不做任何事情,就像它不存在一样,否则会产生一条编译错误,错误位置就是static_assert 语句所在行,错误提示就是第二个参数提示字符串。

#include <iostream>
using namespace std;
static_assert(
    sizeof(void *) !=4,
    "64-bit code generation is not supported."
);
/*
该static_assert 用来确保编译仅在32 位的平台上进行,不支持64 位的平台,该语句可以放在文件的开头处,这样可以尽早检查,以节省失败情况下的编译时间。
*/

2、右值引用&&

前面我们知道虽然指针可以再有指针,但是引用是不能再引用的,所以这里的&&时右值引用。之前我们说过栈对象的返回问题,不能返回栈对象的引用,在栈对象返回的时候,如果没有编译器的优化下-fno-elide-constructors,就会产生临时对象。看下面的程序

#include <iostream>
using namespace std;
class A
{
public:
    A(){
        cout<<"A() "<<this<<endl;
    }
    ~A(){
        cout<<"~A() "<<this<<endl;
    }
    A(const A &another)
    {
        cout<<"A(const A&)"<<&another<<"->"<<this<<endl;
    }
    void dis()
    {
        cout<<"xxxxoooooooooooo"<<endl;
    }
};

A getObjectA()
{
    return A();//这里的A()其实就是一个匿名对象
}

int main(int argc, char *argv[])
{
    A a = getObjectA();
    return 0;
}

这个程序在执行的时候,无编译器优化,会产生两次拷贝构造,第一次产生一个临时变量,第二次再拷贝到新的变量a中。对此C++11,才有了右值引用,增加效率,main函数中这样写:

int main(int argc, char *argv[])
{
    A&& ra =getObjectA();
    return 0;
}

这时只会产生一次拷贝,上面说的第二次拷贝就已经没有了,因为这里右值引用,将原来本应该消失的临时变量利用起来,直接传给了ra。一次拷贝是在产生临时变量的时候产生的,这个是不可避免的,因为,栈对象不能返回引用,返回引用才能避免产生临时对象,避免拷贝。

其实在C++11之前我们也有解决方法,就是const引用。其本质也是产生了临时对象,并且该临时对象是const 的。

int main(int argc, char *argv[])
{
    int& ri = 5;
    const & cri = 5;
    float f = 34.5;
    int & irf = f;
    const int & cirf = f;
    A objA;
    int& irA = objA;
    const int& cirA = objA;
    cout<<cirA<<endl;
    const A & cra = getObjectA();
    cra.dis();
    return 0;
}

如果一个函数的参数是,非const 类型,此时传递临时对象过来,则会编译不过。要想编译能过,则必须加const 修饰。
const引用与右值引用的区别就是const引用产生的临时变量已经是const属性的了,不可更改,而右值引用不是。
所以呀const 函数重载也是在这种反值const 类型的情型下诞生的,const 对象,只能调用const 成员函数。当我们定义了一个对象,但是它是const类型的,这时候普通的成员函数不能使用,只能调用const函数,所以总会有const成员函数重载的现象,这也是const函数重载的意义。

3、移动拷贝

学了移动拷贝之后,类中的默认函数就不再是四个了,而是六个,分别是构造器,析构器,拷贝构造,赋值运算符重载,移动拷贝,移动赋值运算符。那么什么是移动拷贝呢?

#include <iostream>
using namespace std;
class HasPtrMem
{
public:
    HasPtrMem():_d(new int(0))
    {
        cout<<"HasPtrMem()"<<this<<endl;
    }
    HasPtrMem(const HasPtrMem& another)
        :_d(new int(*another._d))
    {
        cout<<"HasPtrMem(const HasPtrMem& another)"<<
               this<<"->"<<&another<<endl;
    }
    HasPtrMem(HasPtrMem &&another)//移动构造
    {
        cout<<this<<" Move resourse from "<<
                 &another<<"->"<<another._d<<endl;
     _d = another._d;
    another._d = nullptr;
    }
    ~HasPtrMem()
    {
        if(_d != nullptr)
            delete _d;
        cout<<"~HasPtrMem()"<<this<<endl;
    }
    int * _d;
};
HasPtrMem getTemp()
{
    return HasPtrMem();
}

栈对象,返回,拷贝不可避免, 能不能减少拷贝的代价??
移动构造函数。我们借用临时变量,将待返回对象的内容"偷"了过来。移动构造的本质,也是一种浅拷贝,但此时的浅拷贝己经限定了其使用的场景,故而是安全的。移动构造函数,最大的用途避免同一份内存数据的不必要的变成两份甚至多份、过程中的变量传递导致的内存复制,另外解除了栈变量对内存的引用。
减少内存的拷贝。

关于默认:

4、std::move

move 的唯一的功能,就是将左值转化为右值,完成移动语义,继而我们可以使用右值。如果说,产生临时对象,隐式的调用了移动构造,std::move 是一种显示调用移动构造的方法。即刻意营造一种可以使用移动的情景。

Moveable n(std::move(m)); //Moveable是一个类,n和m是对象,move之后,m就不可以再用了,m是一个将亡值。

STL 容器对象中的元素,均在堆上,而通常的作法是将栈对象压入,此时完了一次,栈对象的构造,然后再完成一次到堆对象的拷贝。此时效率极低。第二个效率问题,就是像vector 内存不足的时候,自动开辟新的空间,要对原有空间作一次深拷贝,此时效率也是极低的。对象的创建是不可避免的,但是可以移动到容器中去。用移动构造,效率极高

Last modification:October 6th, 2019 at 02:48 pm
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment

One comment

  1. 一博 QQ浏览器 6.2 Android 9 中国 吉林

    哈哈哈哈哈哈哈哈哈