主要对C++实例创建实验过程中发现的一些以前没有太注意的情况,记录小结一下
完整的示例可以查看以下链接:
instance_demo
首先,一个基础的函数,用来打印分隔日志
#include 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 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;}
};
接下来,就是几个和我个人预期不太一样的情况,有些可分析点的用例
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 11new_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,在返回过程也没有实例被创建。
Ref use_static() {static Ref i; // created when called the first timereturn 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 变量是在第一次被调用的时候创建的,那么就有一个额外的问题,这样会存在多线程问题吗?(并未做实验)
void pass_value(Ref in) {
}
void pass_0() {new_section(__FUNCTION__, "");pass_value(Ref()); // interesting, destroyed before pass_0 endcout << __FUNCTION__ << " end" << endl;
}
得到的日志输出
#################################################################
# [pass_0]
#################################################################
Ref() 25
~Ref() 25
pass_0 end
临时对象在该行执行完成后,立马被销毁
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 linecout << __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 exitcout << __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 退出时,才会被销毁。
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& )。
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 将其转换为右值
void tuple_0() {new_section(__FUNCTION__, "");std::tuple t(Ref(), true);// tuple will make its own copy
}
void tuple_1() {new_section(__FUNCTION__, "");std::tuple t(Ref(), true);// tuple will make its own copyauto 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 去封装的话,那么尽量使用指针或者智能指针。
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 instancecout << __FUNCTION__ << " lambda created" << endl;lam();auto f = std::bind(lam); // bind will create another instancecout << __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 库哦。
喜欢此类话题的可以点个赞,点赞多的话,我会继续做类似话题哦