C++对象实例创建实验
迪丽瓦拉
2024-02-29 21:59:03
0

主要对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;}
};

接下来,就是几个和我个人预期不太一样的情况,有些可分析点的用例

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 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,在返回过程也没有实例被创建。

2. 函数内的 static 变量

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 变量是在第一次被调用的时候创建的,那么就有一个额外的问题,这样会存在多线程问题吗?(并未做实验)

3. 临时对象

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

临时对象在该行执行完成后,立马被销毁

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 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 退出时,才会被销毁。

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 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 去封装的话,那么尽量使用指针或者智能指针。

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 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 库哦。

喜欢此类话题的可以点个赞,点赞多的话,我会继续做类似话题哦

相关内容