右值引用、左值引用、指针、RVO优化

这是我第一次真正意义上发技术博客,对C++很多特性我了解的并不深,直到今天我才敢评论总结出来。


上面四个名词,相信很多初学者会疑惑(我也不例外),什么时候做什么事?最开始,我的代码这么写:

int max(int a, int b){ return a>b?a:b; }

int a=1, b=2;

int c = max(a,b);

以上的int是值类型,只占4个字节,万一要是对象类型,也许占100MB空间呢。毫无疑问,我们应该用引用优化。

int const& max(int const& a, int const& b) { return a>b?a:b; }

//use int c = max(a,b);

似乎代码没有什么问题,但隐藏的技巧很深

什么时候用const&, 什么时候不用const&

为什么用引用,不用指针

形参表传进来,传的是值的副本,还是指向值的地址

返回值返回的是值,还是值的副本。


也许有人和我一样,认为问题的答案如下:

const 修改值的时候加, 不修改时不加

引用是别名,指向变量的地址,但他不能改变指向的对象类型,所以很安全

形参传进的是指针是地址,类型则是副本

返回值返回的当然是值的副本


上面的答案没错,问题是怎样优化呢?因此有了上面版本的max,可问题远远不是这样的简单

为什么char *strcpy(char* dest, const char *src); 定义成这样,而不是下面这样:

char * strcpy(const char* src);

这个例子不够恰当,如果是值类型呢

struct cstr{ char* str; size_t len; };

cstr strcpy(const cstr& src);


如果有人和我一样,尝试优化形参、返回值的话,就会发现返回值得到的是副本,而真正的值因为是局部变量在子函数结束也随之销毁,如是指针不delete的话,则永远占用内存了。自然而然,我们想去优化,最好的办法便是形参中使用不加const的指针,或不加const的引用。这也是为什么标准函数strcpy定义成那样的原因。

当然,如果因为害怕而写成那样的话,也不必要,编译器自动进行RVO优化,可以直接返回值。不过为了显式RVO,我一般在最后返回值的时候构造值返回。


似乎问题圆满解决了(话说我为什么加似乎呢?)


C++11出现了,他让我迷茫了。右值引用,有意义吗?他可以优化我们的定义吗?

呃,一开始我见到一个示例,似乎将一个std::string用std::move到另外一个std::string,然后原来的那个string变成空了。呃, 貌似返回值可以用

cstr&& strmove(cstr const& _str){

。。。

return std::move(_str);

}

这样做,与RVO有区别吗?显式定义RVO?


实际上,右值引用完全等同于引用,除了他是右值。啥叫右值?

右值就在右边的,比如

int a=2; //2就是右值

int* a = new int(2);//new int(2)就是右值

这玩意有用吗?是不是右值和我有什么关系,右值可不可以修改,是不是const?

呃,右值应该可以修改,实际上引用、右值引用引发了空前的口水战,无数人认为这是C++类型败笔,过于复杂。(不过我不这么看)

究竟右值引用可以做些什么,std::move可以转移局部变量成为主函数的作用域吗?或者,下面的代码会是我们想要的吗?

class tt{

public:

tt(int*&& pv) : v(pv) {}

print() { printf("x addr:%08x\n", v); if(x!=nullptr) printf("x val:%d\n", *v);  }

private:

int *v;

};

int main(){

int *p = new int(2)

tt test(std::move(p));

//tt test(p);//error

delete p;

p = nullptr;

test.print();

getchar();

return 0;

}

很遗憾,输出结果是x addr:00000000

意味着p的值没有move到tt里面

你没有想错,右值引用,真的就只能限制右边的值

那这个东西有什么用?

这就是本文的重点,改变一下上述代码成这样

      tt(int*& pv, bool hold=false) :v(nullptr) {//注意,不能int*,而是int*&,前者会导致编译器不知道调用哪个版本重载
        printf("use left\n");
        if (pv != nullptr) v = new int(*pv);
        if (hold) {
            delete pv;
            pv = nullptr;
        }
        print();
    }//left ref

    tt(int* const&& pv) :v(nullptr) {
        printf("use right\n");
        print();


        v = pv;
        print();
    }//right ref

...

int* a= new int(11);

tt test1(a);

tt test2(new int(10));

...

好了,世界清净了,test根据不同的左/右值自动处理了。你会问为啥这样写,如果把Int改变成某个对象,你猜上述两个版本谁会调用构造函数和复制构造函数多一点?而且右值引用版本的指针,客户只能new,无法delete(delete由tt类代理),似乎好处++,坏处--


当然了,如果可能,尽量用const&或&,引用的对象存放在栈,除了安全++外,逻辑和效率也会++。

注意上述代码,形参表传的指针,其实也是值副本,不过是地址的值副本。我们需要的是指向地址的地址,所以用引用。搞清楚形参表、返回值、局部变量的作用域、生存周期,及传递方式(_stdcall与_cdecl)。很多复杂的话题都可以从基础开始,C++我认为做的还不错,如果再加上C语言typeof关键字就完美了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值