匿名对象与临时对象

Posted wangkeqin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了匿名对象与临时对象相关的知识,希望对你有一定的参考价值。

关于匿名对象与临时对象,这个概念不是绝对的,概念的区分往往十分拗口难记。要根据作用域,生存时间和用法来来决定;工作多年这些拗口的概念我从来没有真的记住过,也没有一个博客讲清楚他们的区别。下面我们不做概念区分,从逻辑上来论证。

1 无名则无份-临时对象临时生存
2 名正则言顺-起个名字活得久
3 非分之想-不要越界。
4 编译器的能力
5 原则

 

 

假设我们有这样一个类:

class Test
{
public:
    Test(int i = 0):m_test(i)
    {

        cout<<this<<"--construct in ‘Test(int i = 0)‘"<<endl;
    }
    ~Test()
    {
        cout<<this<<"--deconstruct in ‘~Test()‘"<<endl;
    }
    Test(const Test &one)
    {
        m_test = one.m_test;
        cout<<this<<"--copy ‘Test(const Test &one)‘"<<endl;
    }

    Test &operator =(const Test &one)
    {
        if(this == &one)
            return *this;
        m_test = one.m_test;
        cout<<this<<"assign in ‘operator =(const Test &one)‘"<<endl;
    }
    void DisTestVal() const
    {
        cout<<"show value ‘in DisTestVal():‘"<<m_test<<"--at--"<<this<<endl;
    }

    const void * GetThis() const
    {
        return this;
    }

private:
    int m_test;
};

 

下面我们将使用不同的方式来辩证的讨论上面类在使用中构建对象的类型:

临时对象/匿名对象:

1)无名则无份:

int main()
{
    Test(2);
    getchar();
    cout<<"main func finished"<<endl;
}

运行结果:

技术图片     

执行getchar() 

技术图片

匿名对象 的生命周期转瞬即逝,其生存空间就是代码所在的行

看到了吗,在这里我们使用getchar来阻塞main函数的结束,Test(2)这个对象在main函数还没有结束时就析构了,其生命周期昙花一现,其生存空间就时代码所在的行。如果我们构建一个正常的对象于main函数中,其生命周期同main函数。但是匿名对象就不行。没有名字便没有生存的资格。当然我们很少使用这种匿名对象,但是,其可以发起函数调用。如果这个对象对外提供指针,由于其短暂的生命周期与生存空间,等到我们能用指针的时候,它却已经失效了。

 

 

2)名正则言顺:

如何延长一个匿名对象的生命周期呢?他与常规对象的本质区别就是没有一个名字。所以给它取个名字吧。

int main()
{
    const Test &rt = Test(2);   //有了名字,才能活下来
    getchar();
    cout<<"main func finished"<<endl;
}

运行结果:

技术图片

 

 这里我们给Test(2)起了别名,当然它也没有其他的名字,其声明周期便同main了。其效果与Test tobj(2)是一样的,只不过其类型是cosnt Test&的。

 

 

3)非分之想

按照上面的规则,在一个作用域中,一个匿名对象只要有名字,便可以活下来,那如果跨作用域呢?

const Test  ReturnTemp()
{
    const Test & temp = Test(3);
    return temp;
//        return Test(4);
}

int main()
{

    const Test &rtt = ReturnTemp();
    rtt.DisTestVal();
    cout<<"----------------------------"<<endl;
    const Test tt = ReturnTemp(); //这里产生拷贝,但是拷贝之前,临时对象已经被析构了
    tt.DisTestVal();
    getchar();
    cout<<"main func finished"<<endl;
}

 

运行结果:

技术图片

 结果分析:

 以上代码1处已经析构了,我们拿到了一个已经析构对象的引用。2处用这个引用调用函数依然获取到了“正确”值。

使用3处析构对象在4处拷贝构造了另一个对象,函数调用结果也没报错。

 

匿名对象与临时对象都是相对的概念,当这个无名对象产生于自己的作用域空间时,我们叫它匿名对象,一来没有名字,而来确实构造了一个对象。

临时对象定义可以认为是从时间角度出发对一个匿名对象的另一种称谓。在一个作用域内,匿名对象有名字了(const T &类型),不出作用域,其生命周期同作用域。

 

4)编译器的能力问题:

以上用析构的对编译器没有报错,运行结果也无异常,这真是令人恐怖的地方。试想你编写过的程序,是不是运行的一段时间突然闪退,或者突然崩溃呢?

很多这种可怕的错误,编译器是检查不出来的,我在windows和linux使用Qt-ide和 gcc,两者编译都没有报错。所以这种定时炸弹说不定什么时候爆炸了。

编译器能力有限,不要指望编译器能帮你把所有错误检查出来,有时连警告也没有!

 

5)原则:

就此不管临时对象也罢,匿名对象也罢,你只要记住以下原则:

const T &能够接受一个临时对象的引用而不是临时对象!

函数栈上的对象一定不要返回指针或者引用。

很多时候,返回对象并没有太多问题,至于性能问题,Linux和Windows平台都有RVO和NRVO机制。比你想象的性能好很多。关于返值优化这里不展开讨论。

 

以上是关于匿名对象与临时对象的主要内容,如果未能解决你的问题,请参考以下文章

夯实PHP系列购物车代码说明PHP的匿名函数

匿名对象方案与实体对象方案对比

2020/7/8 JAVA总结之:匿名对象/内部类/包的声明与访问/访问修饰符/代码块

面向对象(匿名对象的概述与应用)

匿名对象,内部类,包的声明与访问,访问修饰符,代码块,java的API equals toString方法整理

水合没有查询的中继片段