文章目录
- 1、Terms19:了解临时对象的来源
- 1.1 为了使函数调用成功而进行隐式类型转换的时候
- 1.2 当函数返回对象的时候。
- 1.3 补充说明
- 2、总结
- 3、参考
1、Terms19:了解临时对象的来源
什么是临时对象?
C++真正的临时对象是不可见的匿名对象,不会出现在你的源码中,但是程序在运行时确实生成了这样的对象。
通常出现在以下两种情况:
1.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参数产生临时变量的原因了。
1.2 当函数返回对象的时候。
当函数返回一个对象时,编译器会生成一个临时对象返回,如声明一个函数用来合并两个字符串:
const string strMerge (const string s1, const string s2);
大多时候是无法避免这样的临时变量产生的,但是现代编译器可以将这样的临时变量进行优化掉,这样的优化策略中,有个所谓的“返回值优化”。
1.3 补充说明
1、为什么C++语言禁止为 non-const rference参数产生临时对象
在C++中,禁止为non-const reference参数产生临时对象的主要原因涉及到语言的设计原则、效率和语义清晰性。下面详细解释这几点:
-
设计原则:
C++的设计哲学之一是提供对底层资源的直接控制,同时保持语言的高效性。允许为non-const reference参数产生临时对象会违背这一原则,因为它会引入额外的、可能不必要的临时对象创建和销毁开销。 -
效率:
如果允许为non-const reference参数产生临时对象,那么每次调用这样的函数时,都会创建一个临时对象,并将其地址传递给函数。这不仅会增加额外的内存分配和释放开销,还可能引入额外的拷贝或移动操作。这对于性能敏感的应用程序来说是不可接受的。 -
语义清晰性:
在C++中,引用通常用于表示对某个已存在对象的直接访问。如果允许为non-const reference参数产生临时对象,那么这种直接访问的语义就会变得模糊。程序员可能很难理解函数是在修改一个已存在的对象,还是在修改一个临时的、即将被销毁的对象的副本。 -
避免意外修改:
如果允许non-const reference参数接受临时对象,那么函数内部对引用的修改实际上会作用于一个临时对象上,而不是调用者期望的那个对象。这可能会导致程序行为的不一致和难以追踪的错误。
综上所述,C++禁止为non-const reference参数产生临时对象是为了保持语言的高效性、设计原则的一致性和语义的清晰性。如果函数需要接受一个可能不存在的对象,那么应该使用指针或者const reference作为参数类型,并在函数内部进行相应的空指针检查或只读操作。
2、为什么说看到一个reference-to-const参数,极有可能产生一个临时对象绑定至该参数上
实际上,看到一个reference-to-const
参数时,并不意味着“极可能”一个临时对象会绑定到该参数上。相反,reference-to-const
的设计初衷是为了避免临时对象的创建,因为它允许函数直接引用传入的实参,而无需进行拷贝。
然而,在某些特定情况下,确实可能会有临时对象绑定到reference-to-const
参数上。这通常发生在需要类型转换,且转换的结果是一个临时对象时。下面是一个例子:
#include <iostream>class Base {
public:virtual ~Base() {}virtual void print() const { std::cout << "Base" << std::endl; }
};class Derived : public Base {
public:void print() const override { std::cout << "Derived" << std::endl; }
};void printConstRef(const Base& obj) {obj.print();
}int main() {Derived derived;printConstRef(derived); // 这里没有临时对象,直接引用derived// 下面的调用会导致创建一个Base类型的临时对象printConstRef(Derived()); // 这里创建了一个Derived类型的临时对象,并将其转换为Base类型的临时对象return 0;
}
在这个例子中,printConstRef
函数接受一个const Base&
类型的参数,即一个指向Base
类型的常量引用。在main
函数中,我们首先传递了一个Derived
类型的对象derived
给这个函数,这里没有创建临时对象,因为derived
可以直接被引用。
然而,在printConstRef(Derived())
的调用中,我们传递了一个Derived
类型的临时对象。由于printConstRef
函数接受的是Base
类型的引用,因此会创建一个Base
类型的临时对象来表示这个Derived
类型的临时对象,并将这个Base
类型的临时对象传递给printConstRef
函数。这里确实有一个临时对象被创建并绑定到了reference-to-const
参数上。
但是,这种情况并不是reference-to-const
参数的常见用法,也不是设计者的初衷。通常,我们会尽量避免在需要传递临时对象给reference-to-const
参数的函数调用中创建临时对象,以提高代码的效率。
2、总结
(1)临时对象有构造和析构的成本,影响程序的效率,因此尽可能地消除它们;
(2)更为重要的是很快地发现什么地方会生成临时对象:
(3)当我们看到一个reference-to-const参数时,极可能一个临时对象绑定到该参数上;
(4)当我们看到函数返回一个对象时,就会产生临时对象。
3、参考
《More Effective C++》