程序语义转化与简单优化

2021-08-23  本文已影响0人  404Not_Found

编译器对代码拆分

class X
{
public:
    int m_i;
    X(const X & tmp)
    {
        m_i = tmp.m_i;
        cout << "拷贝构造函数被调用" << endl;
    }
    X()
    {
        m_i = 0;
        cout << "构造函数被调用" << endl;
    }
};

int main(int argc, char ** argv)
{
    X x0;
    x0.m_i = 15;
    
    X x1 = x0;
    X x2(x0);
    X x3 = (x0);

    /*
    编译器会拆分成2个步骤
    X x100 = x0;

    X x100;        //步骤一: 定义一个对象,为对象分配内存,从编译器视角来看,这句是不调用X类的构造函数
    x100.X::X(x0); //步骤二: 直接调用对象的拷贝构造函数。
    */
    return 0;
}

代码运行结果:
1次构造函数, 3次拷贝构造函数。
对于 X x100 = x0 来看。
编译器会拆成2步。 流程如上。

函数的参数带对象

class X
{
public:
    int m_i;
    X(const X & tmp)
    {
        m_i = tmp.m_i;
        cout << "拷贝构造函数被调用" << endl;
    }
    X()
    {
        m_i = 0;
        cout << "构造函数被调用" << endl;
    }
    ~X()
    {
        cout << "析构函数被调用" << endl;
    }
};

void func(X tmp)
{
    return;
}

int main(int argc, char ** argv)
{
    X x0;
    func(x0);
    return 0;
}

结果:


结果.png

拷贝构造,给的是 函数参数 X tmp。
第一次析构给的也是 函数参数 X tmp
第二次给的是 main 中的 x0

返回值带对象

返回值带对象.png

拷贝构造函数发生于 return x0, x0 会拷贝给一个临时对象。F9 调试就知道了。
析构给一个给的 函数里的,一个给的main 函数中的X my

class X
{
public:
    int m_i;
    X(const X & tmp)
    {
        m_i = tmp.m_i;
        cout << "拷贝构造函数被调用" << endl;
    }
    X()
    {
        m_i = 0;
        cout << "构造函数被调用" << endl;
    }
    ~X()
    {
        cout << "析构函数被调用" << endl;
    }

    void functest()
    {
        cout << "functest()" << endl;
    }
};

X func()
{
    X x0;
    return x0;// 执行完毕后有一个拷贝构造函数 和一个析构函数, X tmp = x0 ,析构函数,析构了x0 
}

/*
    void func(X & extra)
    {
        X x0;
        extra.X::X(x0);
        return;
    }
*/

int main(int argc, char ** argv)
{

    X my = func();//这里倒是没有拷贝构造函数
    /*
    X my
    func(my)// 修改了my的值
    */

    func().functest();
    /*
    X my;
    (func(my), my).functest();//逗号表达式: 先计算表达式1, 再计算表达式2,最后调用functest
    */
    
    X(*pf)();
    pf = func;
    pf().functest();
    /*
    X my;
    void (*pf)(X&);
    pf = func;
    pf(my);
    my.functest();
    */

    return 0;
}
  1. 对于 X my = func(), 编译器会将func 进行改造。
    所以我们从程序员视角看到了一次 函数中 的拷贝构造,析构了x0, 析构了my
  2. 对于 func().funtest() 的解读

其实我觉得这里没必要研究太深,反而会把自己绕进去。没必要。了解就好

简单优化

class CTempValue
{
public:
    int val1;
    int val2;
public:
    CTempValue(int v1 = 0, int v2 = 0) :val1(v1), val2(v2)
    {
        cout << "调用了构造函数" << this<<endl;
        cout << "val1 =" << val1 << endl;
        cout << "val2 =" << val2 << endl;
    }

    CTempValue(const CTempValue &t) : val1(t.val1), val2(t.val2)
    {
        cout << "调用了拷贝构造函数" << this << endl;
    }

    virtual ~CTempValue()
    {
        cout << "调用了析构函数" << this << endl;
    }
};

CTempValue Double(CTempValue &ts)
{
    /*
        显然会多调用一次构造函数,拷贝构造函数和 析构函数
    */
    CTempValue tmpm;
    tmpm.val1 = ts.val1 * 2;
    tmpm.val2 = ts.val2 * 2;
    return tmpm;
}

int main(int argc, char ** argv)
{
    CTempValue ts1(10, 20);
    //Double(ts1);// 没有东西去接,就是临时对象, 这行结束,临时对象析构
    CTempValue ts2 = Double(ts1); // 不会多调用一次临时对象的析构
    //ts1 = Double(ts1);// 会调用一次 析构, 析构临时对象
    return 0;
}

函数Double 是有优化空间的。
同时,以上代码 析构顺序如下:


析构顺序.png

另外再填一句:看main 函数中的注释部分。用 ts2 和 ts1 去接的话,析构函数调用次数会有不同。
ts1 去接会明显多一次析构,析构的是从函数中返回的临时对象

Double 函数明显有缺陷,会多调用一次构造 + 拷贝构造(临时对象) + 析构

CTempValue Double(CTempValue &ts)
{
    return CTempValue(ts.val1, ts.val2);//生成一个临时对象
}

只会调用一次 构造函数

g++ -fno-elide-constructors *.cpp

测试

    clock_t start, end;
    start = clock(); //以毫秒为单位
    for(int i = 0; i < 100000; i++)
    {
        Double(ts1);
    }
    end = clock();
    cout<<end - start<<endl;

测一下就知道了。大概有50ms的差距

linux 与 windows 对于拷贝函数的优化

class X
{
public:
    int m_i;
    X(const X & tmp)
    {
        m_i = tmp.m_i;
        cout << "拷贝构造函数被调用" << endl;
    }
    X()
    {
        m_i = 0;
        cout << "缺省构造函数被调用" << endl;
    }
    X(int value) :m_i(value)
    {
        cout<<"X(int) 构造函数被调用"<<endl;
    }

    ~X()
    {
        cout << "析构函数被调用" << endl;
    }
};

int main(int argc, char **argv)
{
    cout << "-----------begin---------------" << endl;
    X x10(1000);
    cout << "--------------------------" << endl;
    X x11 = 1000;//涉及隐式转换, 加上explicit 就不行啦
    cout << "--------------------------" << endl;
    X x12 = X(1000);//vs2017 把拷贝构造了优化le ?
    cout << "--------------------------" << endl;
    X x13 = (X)1000;
    cout << "-----------end---------------" << endl;
    return 0;
}
/*
    编译器角度 看 X12 = X(1000);
    X _tmp;
    _tmp.X::X(1000);
    X x12;
    x12.X::X(_tmp);//拷贝构造函数被调用
    _tmp0.X::~X();//调用析构
*/

拷贝构造函数必须吗?

如果不涉及 指针的话,仅简单类型,则不需要,因为类本身支持 bitwise(按位拷贝)

class X
{
public:
    int m_i;
    X()
    {
        m_i = 0;
        cout << "缺省构造函数被调用" << endl;
    }
    X(int value) :m_i(value)
    {
        cout << "X(int) 构造函数被调用" << endl;
    }

    ~X()
    {
        cout << "析构函数被调用" << endl;
    }
};

int main(int argc, char **argv)
{
    X x0;
    x0.m_i = 150;
    X x1(x0);
    cout << x1.m_i << endl;
    return 0;
}

则不会调用拷贝构造函数,但依旧会得到正确的值

何时需要拷贝构造

深拷贝,否则会造成重复析构

class X
{
public:
    int m_i;
    int *p_m;

    X(const X* tmp)
    {
        //增加拷贝构造函数会使得 bitwise 失效
        p_m = new int(0);
        memcpy(p_m, tmp->p_m, sizeof(int));
        m_i = tmp->m_i;

        cout << "拷贝构造函数被调用" << endl;
    }

    X()
    {
        m_i = 0;
        p_m = new int(100);
        cout << "缺省构造函数被调用" << endl;
    }
    X(int value) :m_i(value)
    {
        cout << "X(int) 构造函数被调用" << endl;
    }

    ~X()
    {
        delete p_m;
        cout << "析构函数被调用" << endl;
    }
};

int main(int argc, char **argv)
{
    X x0;
    x0.m_i = 150;
    X x1(x0);
    //非常明显的 两个对象重复析构了 一片内存。所以才需要引入拷贝构造函数
    cout << x1.m_i << endl;
    return 0;
}
上一篇 下一篇

猜你喜欢

热点阅读