一、临时对象的生命周期
T c=a+b
假设T是一个类型,那么上述代码执行时,首先会产生一个临时对象用来存放a+b的结果(拷贝初始化临时对象),然后用该临时对象拷贝初始化c,最后临时对象被释放。如果开启编译器优化选项,那么会直接用a+b的结果初始化c
临时性对象被销毁的时机应该是对表达式求值过程中的最后一个步骤。
比如下面的代码
int main()
{
string s1="123";
string s2="qwe";
bool flag;
cin>>flag;
string res=flag?s1+s2:"";
return 0;
}
上述代码中保存s1+s2的临时对象并不会马上被销毁,而是等res初始化完成后才被销毁
下面这段代码输出的p是无效值,但是经过测试后,发现并不是(我也整不明白为啥了。。。)
int main()
{
string s1="123";
string s2="qwe";
const char *p=(s1+s2).c_str();
cout<<p<<endl;
return 0;
}
此外,如果一个临时对象和引用绑定,那么,临时对象的生命周期将会被提升,直到被初始化的引用的生命周期结束
示例见https://blog.csdn.net/Master_Cui/article/details/106353580
template<class T>
T funadd(const T& a, const T& b)
{
T addhe = a + b;
return addhe;
}
int main()
{
return 0;
}
上述代码没有调用函数模板,对应的反汇编代码中,可以发现,没有找到funadd的代码,而且,在网站https://godbolt.org/上试验,如果一个模板函数没有被实例化,那么将不会产生汇编代码
因为没有调用函数模板,所以,没有实参推断和实例化的过程,所以在可执行文件中也就不会有相应的代码产生,这么做的目的是为了防止因为代码膨胀导致可执行文件过大
当调用了funcadd后
可见,实例化之后,编译器才会产生对应的代码。
上述机制同样适合类模板及类模板中的非虚成员函数
如果定义了一个模板类的指针,那么也不会产生实际的类对象的代码,因为指针指向的内容可以无效,这就是为什么声明了一个不完整的类后,可以声明相应的指针,因为指针只是内置类型,指向的数据不保证有效
上述机制有个例外,那就是模板类中的虚函数
示例
template<class T>
class ATPL
{
public:
virtual void func1() { cout << "ATPL::func1()" << endl; }
};
int main()
{
ATPL<int> t;
return 0;
}
上述代码虽然没有使用func1,但是编译器依然会为其产生代码,因为要将该函数的地址放到虚表中,所以不得不产生代码
三、内联函数
inline只是个请求,如果这项请求被接受,编译器就认为它可以用一个表达式将函数展开。
处理一个inline函数,有两个阶段:1.分析函数定义内容。如果函数因其复杂度或建构问题,被判断不可成为inline,它会被转为一个static函数,并在产生对应的函数定义。2.如果可以inline,那么在调用点上,函数会进行扩展操作。如果内联函数被展开为表达式的次数太多,会产生过多的代码,导致程序内存上升。如果编译器把一个函数内联,那么也有可能会因为接受函数参数而产生一些临时变量,也会
通过网站https://godbolt.org/可知,内联函数如果被编译器内联优化,并不会产生反汇编代码
但是上述代码的在没有开启优化选项时的反汇编代码如下
可见,并没有进行汇编优化,因为依然跳转到函数栈帧进行函数调用,如果想让编译器进行内联优化,需要添加-O开启优化选项
g++ -g -O -fno-elide-constructors -Wall c++model.cpp
此时的反汇编代码如下
此时在文件中已经查不到add_inline的调用,说明add_inline已经被内联优化了
参考
《深度探索C++对象模型》
《C++新经典:对象模型》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出