“非常量引用的初始值必须为左值“及“匿名对象“

“非常量引用的初始值必须为左值"及"匿名对象”

前言

  • 在练习的时候用引用接收匿名对象引发”非常量引用的初始值必须为左值“错误
  • 简单还原如下
#include<iostream>
using namespace std;
class Maker {
public:
	Maker() {
		cout << "Maker的构造" << endl;
	}
	Maker(const Maker& m) {
		cout << "Maker的拷贝构造" << endl;
	}
	~Maker() {
		cout << "~Maker的析构" << endl;
	}
	void print() {
		cout << "hell0" << endl;
	}
};
//用引用接收匿名对象,将引发“非常量引用的初始值必须为左值”
Maker& fun() //只有将返回类型改为const Maker& 方可正确接收
{
	return Maker();
}
void test01(){
    Maker a=fun();
    //不管是用函数返回值初始化还是如下操作,都会报错,“非常量引用的初始值必须为左值”
    Maker b=Maker();
}

int main(void){
    test01();
}
  • 经查得知,C++的匿名对象是prvalue(纯右值),无法作为引用传递。
  • 若想引用接收匿名对象只有用reference-to-const接收
  • C++Primer(5th)中说道,当一个对象被用作右值时,用的是对象的值,当对象被用作左值的时候,用的是对象的身份。

匿名对象

  • 借此,深入了解下匿名对象

  • 什么是临时对象?

    ​ C++真正的临时对象是不可见的匿名对象,不会出现在你的源码中,但是程序在运行时确实生成了这样的对象.

    通常出现在以下两种情况:

    (1)为了使函数调用成功而进行隐式类型转换的时候

    ​ 传递某对象给一个函数,而其类型与函数的形参类型不同时,如果可以通过隐式转化的话可以使函数调用成功,那么此时会通过构造函数生成一个临时对象,当函数返回时临时对象即刻自动销毁。如下

    //计算字符ch在字符串str中出现的次数
    int countChar (const string& str, char ch);
    char buffer[];
    char c;
    //调用上面的函数
    countChar (buffer, c);
    

    我们看的第一个参数为char[],而函数的参数类型为const string&,参数不一致,看看能否进行隐式转化,string类有个构造函数是可以作为隐式转化函数的。那么编译器会产生一个string的临时变量,以buffer为参数进行构造,那么countChar中的str参数会绑定到此临时变量上,直到函数返回时销毁。

    注意这样的转化只会出现在两种情况下:函数参数以传值(by value)的方式传递 或者 对象被传递到一个 reference-to-const 参数上。

    传值方式:

    
    int countChar (string str, char ch);
    string buffer;
    char c;
    //参数通过传值方式传递
    countChar (buffer, c);
    
    

    ​ 这种方法会调用string的拷贝构造函数生成一个临时变量,再将这个临时变量绑定到str上,函数返回时进行销毁。

    传常量引用:

    ​ 开始的实例即时属于这种情况,但一定强调的是传递的是const型引用,如将开始函数的原型改为

    int countChar (string& str, char ch);
    

    ​ 下面调用相同,编译器会报错!为什么C++设计时要求 当对象传递给一个reference-to-non-const 参数不会发生隐式类型转化呢?

    ​ 下面的实例可能向你说明这样设计的目的:

    
    //声明一个将str中字符全部转化为大写
    void toUpper (string& str);
    char buffer[] = "hazirguo";
    toUpper(buffer);                 //error!!非const引用传递参数不能完成隐式转化
    
    

    ​ 如果编译器允许上面的传递完成,那么,会生成一个临时对象,toUpper函数将临时变量的字符转化为大写,返回是销毁对象,但是对buffer内容毫无影响!程序设计的目地是期望对“非临时对象”进行修改,而如果对reference-to-non-cosnt对象进行转化,函数只会对临时变量进行修改。这就是为什么C++中要禁止non-const-reference参数产生临时变量的原因了。

    (2)当函数返回对象的时候

    ​ 当函数返回一个对象时,编译器会生成一个临时对象返回,如声明一个函数用来合并两个字符串:

    const string strMerge (const string s1, const string s2);
    大多时候是无法避免这样的临时变量产生的,但是现代编译器可以将这样的临时变量进行优化掉,这样的优化策略中,有个所谓的“返回值优化”
    总结:
    临时对象有构造和析构的成本,影响程序的效率,因此尽可能地消除它们。而更为重要的是很快地发现什么地方会生成临时对象:
    
    • 当我们看到一个reference-to-const参数时,极可能一个临时对象绑定到该参数上;

    • 当我们看到函数返回一个对象时,就会产生临时对象。

  • 匿名对象的生命周期

  • 调用匿名对象初始化另一同一类型对象时,匿名对象被接收;若赋值给另一同一类型对象(对象已初始化过)时,匿名对象被析构。

    #include<iostream>
    using namespace std;
    class Maker {
    public:
    	Maker() {
    		cout << "Maker的构造" << endl;
    	}
    	Maker(const Maker& m) {
    		cout << "Maker的拷贝构造" << endl;
    	}
    	~Maker() {
    		cout << "~Maker的析构" << endl;
    	}
    	void print() {
    		cout << "hell0" << endl;
    	}
    };
    
    Maker fun()
    {
    	return Maker();
    }
    //调用test01()输出:-----------------------
    //Maker的构造
    //Maker的构造
    //~Maker的析构
    //~Maker的析构
     void test01() {
    	 Maker a;
    	 a = fun();//是否用函数来赋值都结果都相同,等同于a=Maker();
    }
    
    //调用test02()输出:----------------------
    //Maker的构造
    //~Maker的析构
     void test02(){
    	 Maker a = fun();//是否用函数来赋值都结果都相同,等同于Maker a=Maker();
     }
    	
     int main()
     {
    	 test01();
    	// test02();
    	 return 0;
    }
    
  • 另外,函数的返回值是一个对象时,返回类型也是对象时,返回的是函数内部的局部变量本身,还是会产生一个中间对象(匿名对象)呢?

#include<iostream>
using namespace std;
class Maker {
public:
	Maker() {
		cout << "Maker的构造" << endl;
	}
	Maker(const Maker& m) {
		cout << "Maker的拷贝构造" << endl;
	}
	~Maker() {
		cout << "~Maker的析构" << endl;
	}
	void print() {
		cout << "hell0" << endl;
	}
};

Maker fun()
{	Maker a();//调用Maker的构造
	return a;//调用Maker的拷贝构造
}	//a被析构
void test01()
{
    Maker b=fun();
} //b被析构

//输出结果
//Maker的构造
//Maker的拷贝构造
//~Maker的析构
//~Maker的析构
 int main()
 {
	 test01();
	 return 0;
}
//经VS2019下的测试可得,返回的是一个匿名对象,而匿名对象被接收,即有名则存,生命周期被延长
  • 当函数返回引用类型时不能返回局部变量和形参只能返回this指针所指向的对象,返回的是对象本身,因此执行return语句时不会生成匿名对象也就没必要调用拷贝构造函数完成匿名对象的初始化,所以当函数返回值类型是对象引用时不会调用拷贝构造函数。
//若改为如下代码,只会生成一份对象,引用延长了对象的生命周期,按理说函数里的局部变量的生命应随函数的结束而结束,这是因为编译器做了优化策略
Maker& fun()
{
	Maker a;
	return a;
}
void test01()
{
	Maker& a = fun();
}

如有错误或遗漏,希望不吝指出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值