主要对C++实例创建实验过程中发现的一些以前没有太注意的情况,记录小结一下
完整的示例可以查看以下链接:
首先,一个基础的函数,用来打印分隔日志
#include <string>
using std::string;
using std::cout;
using std::endl;
void new_section(string section, string msg) {
cout << endl;
cout << "#################################################################" << endl;
cout << "# [" << section << "] " << msg << endl;
cout << "#################################################################" << endl;
}
再就是主角类,Ref,用来记录构建和析构调用的(就是对调用函数入口进行日志记录)
// ref.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;
class Ref final
{
public:
uint32_t m_id{0};
static uint32_t m_instCount;
Ref()
{
m_id = m_instCount++;
cout << "Ref() " << m_id << endl;
}
Ref(Ref&& )
{
m_id = m_instCount++;
cout << "Ref(Ref&& ) " << m_id << endl;
}
Ref(const Ref&& )
{
m_id = m_instCount++;
cout << "Ref(const Ref&& ) " << m_id << endl;
}
Ref(Ref& )
{
m_id = m_instCount++;
cout << "Ref(Ref& ) " << m_id << endl;
}
Ref(const Ref& )
{
m_id = m_instCount++;
cout << "Ref(const Ref& ) " << m_id << endl;
}
Ref& operator=(const Ref& i)
{
cout << "Ref& operator=(const Ref&) " << m_id << endl;
return (*this);
}
Ref& operator=(const Ref&& i)
{
cout << "Ref& operator=(const Ref&&) " << m_id << endl;
return (*this);
}
~Ref() {
cout << "~Ref() " << m_id << endl;
}
};
接下来,就是几个和我个人预期不太一样的情况,有些可分析点的用例
1. 级联返回的情况
Ref create_1() {
return Ref();
}
Ref create_4() {
Ref i = create_1();
return i; // interesting, no Ref&& construction
}
void return_11() {
new_section(__FUNCTION__, "");
create_4();
}
void return_12() { // interesting, same as 11
new_section(__FUNCTION__, "");
auto i = create_4();
}
得到输出日志:
#################################################################
# [return_11]
#################################################################
Ref() 16
~Ref() 16
#################################################################
# [return_12]
#################################################################
Ref() 17
~Ref() 17
从 return_11 / return_12 示例看出,返回过程会直接将该实例返回,不会有额外的创建过程,即使是级联情况,也是一样的。
另外从 return_12 来看,返回的实例会直接指定给变量 i,综合 return_11,不管有没有变量去保存返回的实例,都没有额外的实例被创建。
另外,create_4 里面,即使返回的实例先给到变量 i,在返回过程也没有实例被创建。
2. 函数内的 static 变量
Ref use_static() {
static Ref i; // created when called the first time
return i;
}
void return_18() {
new_section(__FUNCTION__, "");
auto i = use_static();
}
void return_19() {
new_section(__FUNCTION__, "");
auto i = use_static();
}
得到输出日志
#################################################################
# [return_18]
#################################################################
Ref() 22
Ref(Ref& ) 23
~Ref() 23
#################################################################
# [return_19]
#################################################################
Ref(Ref& ) 24
~Ref() 24
其实 return_18 / return_19 两个用例完全一样,只是为了日志区分一下,可以看到,函数里的 static 变量是在第一次被调用的时候创建的,那么就有一个额外的问题,这样会存在多线程问题吗?(并未做实验)
3. 临时对象
void pass_value(Ref in) {
}
void pass_0() {
new_section(__FUNCTION__, "");
pass_value(Ref()); // interesting, destroyed before pass_0 end
cout << __FUNCTION__ << " end" << endl;
}
得到的日志输出
#################################################################
# [pass_0]
#################################################################
Ref() 25
~Ref() 25
pass_0 end
临时对象在该行执行完成后,立马被销毁
4. 返回实例的销毁时间
Ref pass_and_return_value(Ref in) {
cout << "pass_and_return_value" << endl;
return in;
}
void pr_0() {
new_section(__FUNCTION__, "");
pass_and_return_value(Ref()); // Ref() will be destroyed before return value
// return value destroy, after last line
cout << __FUNCTION__ << " end" << endl;
}
void pr_1() {
new_section(__FUNCTION__, "");
auto o = pass_and_return_value(Ref()); // return value is held by o
// o will be destroyed when pr_1 exit
cout << __FUNCTION__ << " end" << endl;
}
得到的日志输出
#################################################################
# [pr_0]
#################################################################
Ref() 37
pass_and_return_value
Ref(Ref&& ) 38
~Ref() 37
~Ref() 38
pr_0 end
#################################################################
# [pr_1]
#################################################################
Ref() 39
pass_and_return_value
Ref(Ref&& ) 40
~Ref() 39
pr_1 end
~Ref() 40
从 pr_0 可以看到,传入的实参临时对象实例Ref(),会在函数调用后率先被析构(与前面的结论一样)。不过还可以看到,返回的实例也将在函数调用的一行执行完成后被立刻销毁。
从 pr_1 来看(对比 pr_0),如果有变量获取了返回对象实例,那么该实例则不会被立刻销毁,而是在 pr_1 退出时,才会被销毁。
5. 右值作为构造源
void pass_right_to_inst_assign(Ref&& in) {
Ref i = in; // created with Ref(Ref& )
}
void inst_2() {
new_section(__FUNCTION__, "");
pass_right_to_inst_assign(Ref());
}
void pass_left_to_inst_assign(Ref& in) {
Ref i = in; // created with Ref(Ref& )
}
void inst_3() {
new_section(__FUNCTION__, "");
pass_left_to_inst_assign(Ref());
}
得到的日志输出
#################################################################
# [inst_2]
#################################################################
Ref() 66
Ref(Ref& ) 67
~Ref() 67
~Ref() 66
#################################################################
# [inst_3]
#################################################################
Ref() 68
Ref(Ref& ) 69
~Ref() 69
~Ref() 68
可以看到,不论是左值传递的入参,还是右值传递的入参,其实在作为构造源的时候,都是调用的 Ref(Ref& )。
6. 右值的赋值操作
Ref global;
void pass_right_to_global(Ref&& in) {
global = in; // Ref& operator=(const Ref&) will be called
}
void inst_4() {
new_section(__FUNCTION__, "");
pass_right_to_global(Ref());
}
void move_right_to_global(Ref&& in) {
global = std::move(in); // Ref& operator=(const Ref&&) will be called
}
void inst_5() {
new_section(__FUNCTION__, "");
move_right_to_global(Ref());
}
得到的日志输出
#################################################################
# [inst_4]
#################################################################
Ref() 70
Ref& operator=(const Ref&) 0
~Ref() 70
#################################################################
# [inst_5]
#################################################################
Ref() 71
Ref& operator=(const Ref&&) 0
~Ref() 71
inst_4 和 inst_5 来看,以右值的形式将实参传入以后,赋值操作中,如果希望以 Ref&& 形式进行,那么仍然需要使用 std::move 将其转换为右值
7. tuple
void tuple_0() {
new_section(__FUNCTION__, "");
std::tuple<Ref, bool> t(Ref(), true);
// tuple will make its own copy
}
void tuple_1() {
new_section(__FUNCTION__, "");
std::tuple<Ref, bool> t(Ref(), true);
// tuple will make its own copy
auto i = std::get<0>(t);
// get will make its own copy again
}
得到的日志输出
#################################################################
# [tuple_0]
#################################################################
Ref() 72
Ref(Ref&& ) 73
~Ref() 72
~Ref() 73
#################################################################
# [tuple_1]
#################################################################
Ref() 74
Ref(Ref&& ) 75
~Ref() 74
Ref(Ref& ) 76
~Ref() 76
~Ref() 75
从两个示例看到,创建 tuple 的时候会构造一个实例,从 tuple 中取出的时候,还会构造一个实例。
实践中,如果 tuple 的组成,不是指针或者智能指针的话,尽量还是不要使用 tuple 为好。
或者说,想要使用 tuple 去封装的话,那么尽量使用指针或者智能指针。
8. lambda表达式
void lambda_3() {
new_section(__FUNCTION__, "");
Ref i;
auto f = [=]() { // catch with Ref(const Ref& )
cout << "lambda call (i = " << i.m_id << ")" << endl;
};
cout << __FUNCTION__ << " lambda created" << endl;
f();
}
得到的日志输出
#################################################################
# [lambda_3]
#################################################################
Ref() 97
Ref(const Ref& ) 98
lambda_3 lambda created
lambda call (i = 98)
~Ref() 98
~Ref() 97
从 lambda_3 可以看到,[=] 值捕捉的时候,会调用 Ref(const Ref& ) 构造
void lambda_6() {
new_section(__FUNCTION__, "");
Ref i;
auto lam = [=]() {
cout << "lambda call (i = " << i.m_id << ")" << endl;
}; // lambda will catch an instance
cout << __FUNCTION__ << " lambda created" << endl;
lam();
auto f = std::bind(lam); // bind will create another instance
cout << __FUNCTION__ << " binded" << endl;
f();
}
得到的日志输出
#################################################################
# [lambda_6]
#################################################################
Ref() 103
Ref(const Ref& ) 104
lambda_6 lambda created
lambda call (i = 104)
Ref(const Ref& ) 105
lambda_6 binded
lambda call (i = 105)
~Ref() 105
~Ref() 104
~Ref() 103
从 lambda_6 可以看到,创建 lambda 表达式的时候,已经复制了一个实例副本, bind 操作会再创建一个实例副本
小结
C++实例创建的部分,本文并非完整的列举,感兴趣的朋友欢迎把我的 demo 下下来,再加几个测试测试,也欢迎提 MR,帮我丰富 demo 库哦。
喜欢此类话题的可以点个赞,点赞多的话,我会继续做类似话题哦