• C++对象实例创建实验


    主要对C++实例创建实验过程中发现的一些以前没有太注意的情况,记录小结一下

    完整的示例可以查看以下链接:

    instance_demo

    首先,一个基础的函数,用来打印分隔日志

    1. #include
    2. using std::string;
    3. using std::cout;
    4. using std::endl;
    5. void new_section(string section, string msg) {
    6. cout << endl;
    7. cout << "#################################################################" << endl;
    8. cout << "# [" << section << "] " << msg << endl;
    9. cout << "#################################################################" << endl;
    10. }

    再就是主角类,Ref,用来记录构建和析构调用的(就是对调用函数入口进行日志记录)

    1. // ref.h
    2. #pragma once
    3. #include
    4. using std::cout;
    5. using std::endl;
    6. class Ref final
    7. {
    8. public:
    9. uint32_t m_id{0};
    10. static uint32_t m_instCount;
    11. Ref()
    12. {
    13. m_id = m_instCount++;
    14. cout << "Ref() " << m_id << endl;
    15. }
    16. Ref(Ref&& )
    17. {
    18. m_id = m_instCount++;
    19. cout << "Ref(Ref&& ) " << m_id << endl;
    20. }
    21. Ref(const Ref&& )
    22. {
    23. m_id = m_instCount++;
    24. cout << "Ref(const Ref&& ) " << m_id << endl;
    25. }
    26. Ref(Ref& )
    27. {
    28. m_id = m_instCount++;
    29. cout << "Ref(Ref& ) " << m_id << endl;
    30. }
    31. Ref(const Ref& )
    32. {
    33. m_id = m_instCount++;
    34. cout << "Ref(const Ref& ) " << m_id << endl;
    35. }
    36. Ref& operator=(const Ref& i)
    37. {
    38. cout << "Ref& operator=(const Ref&) " << m_id << endl;
    39. return (*this);
    40. }
    41. Ref& operator=(const Ref&& i)
    42. {
    43. cout << "Ref& operator=(const Ref&&) " << m_id << endl;
    44. return (*this);
    45. }
    46. ~Ref() {
    47. cout << "~Ref() " << m_id << endl;
    48. }
    49. };

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

    1. 级联返回的情况

    1. Ref create_1() {
    2. return Ref();
    3. }
    4. Ref create_4() {
    5. Ref i = create_1();
    6. return i; // interesting, no Ref&& construction
    7. }
    8. void return_11() {
    9. new_section(__FUNCTION__, "");
    10. create_4();
    11. }
    12. void return_12() { // interesting, same as 11
    13. new_section(__FUNCTION__, "");
    14. auto i = create_4();
    15. }

    得到输出日志: 

    #################################################################

    # [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 变量

    1. Ref use_static() {
    2. static Ref i; // created when called the first time
    3. return i;
    4. }
    5. void return_18() {
    6. new_section(__FUNCTION__, "");
    7. auto i = use_static();
    8. }
    9. void return_19() {
    10. new_section(__FUNCTION__, "");
    11. auto i = use_static();
    12. }

    得到输出日志

    #################################################################

    # [return_18]

    #################################################################

    Ref() 22

    Ref(Ref& ) 23

    ~Ref() 23

    #################################################################

    # [return_19]

    #################################################################

    Ref(Ref& ) 24

    ~Ref() 24

    其实 return_18 / return_19 两个用例完全一样,只是为了日志区分一下,可以看到,函数里的 static 变量是在第一次被调用的时候创建的,那么就有一个额外的问题,这样会存在多线程问题吗?(并未做实验)

    3. 临时对象

    1. void pass_value(Ref in) {
    2. }
    3. void pass_0() {
    4. new_section(__FUNCTION__, "");
    5. pass_value(Ref()); // interesting, destroyed before pass_0 end
    6. cout << __FUNCTION__ << " end" << endl;
    7. }

    得到的日志输出

    #################################################################

    # [pass_0]

    #################################################################

    Ref() 25

    ~Ref() 25

    pass_0 end

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

    4. 返回实例的销毁时间

    1. Ref pass_and_return_value(Ref in) {
    2. cout << "pass_and_return_value" << endl;
    3. return in;
    4. }
    5. void pr_0() {
    6. new_section(__FUNCTION__, "");
    7. pass_and_return_value(Ref()); // Ref() will be destroyed before return value
    8. // return value destroy, after last line
    9. cout << __FUNCTION__ << " end" << endl;
    10. }
    11. void pr_1() {
    12. new_section(__FUNCTION__, "");
    13. auto o = pass_and_return_value(Ref()); // return value is held by o
    14. // o will be destroyed when pr_1 exit
    15. cout << __FUNCTION__ << " end" << endl;
    16. }

    得到的日志输出

    #################################################################

    # [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. 右值作为构造源

    1. void pass_right_to_inst_assign(Ref&& in) {
    2. Ref i = in; // created with Ref(Ref& )
    3. }
    4. void inst_2() {
    5. new_section(__FUNCTION__, "");
    6. pass_right_to_inst_assign(Ref());
    7. }
    8. void pass_left_to_inst_assign(Ref& in) {
    9. Ref i = in; // created with Ref(Ref& )
    10. }
    11. void inst_3() {
    12. new_section(__FUNCTION__, "");
    13. pass_left_to_inst_assign(Ref());
    14. }

    得到的日志输出

    #################################################################

    # [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. 右值的赋值操作

    1. Ref global;
    2. void pass_right_to_global(Ref&& in) {
    3. global = in; // Ref& operator=(const Ref&) will be called
    4. }
    5. void inst_4() {
    6. new_section(__FUNCTION__, "");
    7. pass_right_to_global(Ref());
    8. }
    9. void move_right_to_global(Ref&& in) {
    10. global = std::move(in); // Ref& operator=(const Ref&&) will be called
    11. }
    12. void inst_5() {
    13. new_section(__FUNCTION__, "");
    14. move_right_to_global(Ref());
    15. }

    得到的日志输出

    #################################################################

    # [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

    1. void tuple_0() {
    2. new_section(__FUNCTION__, "");
    3. std::tuplebool> t(Ref(), true);
    4. // tuple will make its own copy
    5. }
    6. void tuple_1() {
    7. new_section(__FUNCTION__, "");
    8. std::tuplebool> t(Ref(), true);
    9. // tuple will make its own copy
    10. auto i = std::get<0>(t);
    11. // get will make its own copy again
    12. }

    得到的日志输出

    #################################################################

    # [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表达式

    1. void lambda_3() {
    2. new_section(__FUNCTION__, "");
    3. Ref i;
    4. auto f = [=]() { // catch with Ref(const Ref& )
    5. cout << "lambda call (i = " << i.m_id << ")" << endl;
    6. };
    7. cout << __FUNCTION__ << " lambda created" << endl;
    8. f();
    9. }

     得到的日志输出

    #################################################################

    # [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& ) 构造

    1. void lambda_6() {
    2. new_section(__FUNCTION__, "");
    3. Ref i;
    4. auto lam = [=]() {
    5. cout << "lambda call (i = " << i.m_id << ")" << endl;
    6. }; // lambda will catch an instance
    7. cout << __FUNCTION__ << " lambda created" << endl;
    8. lam();
    9. auto f = std::bind(lam); // bind will create another instance
    10. cout << __FUNCTION__ << " binded" << endl;
    11. f();
    12. }

     得到的日志输出

    #################################################################

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

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

  • 相关阅读:
    JAVA内部类
    goland-使用wsl 远程编译 控制台输出问题
    电容元件符号与工作原理:电子电路中的电荷储存利器 | 百能云芯
    LeetCode 0318. 最大单词长度乘积
    力扣日记11.7-【二叉树篇】二叉树的层序遍历
    (a /b)*c的值
    number
    招聘网站实现
    JavaScript -- 数组常用方法及示例代码总结
    网络编程启蒙
  • 原文地址:https://blog.csdn.net/davied9/article/details/128063047