C++对象实例创建实验

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

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

instance_demo

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值