C++ 赋值构造函数 临时变量 临时对象 之一

为了验证C++中的复制构造函数和临时对象的问题,我写了一个小程序来研究了一下:

//复制构造函数与临时对象
#include <iostream>
using namespace std;

class A
{
    public:
    A(){cout<<"A is construting..."<<endl;}
    A(const A& a){cout<<"A is cloning..."<<endl;}
    ~A(){cout<<"A is deconstructing..."<<endl;}
};

A func( A a)
{
     return a;
}
int main()
{
    A a;
    //situation 1
    cout<<endl<<"situation 1"<<endl;
    cout<<endl<<"用对象a来初始化b,复制构造函数起到了构造函数的作用"<<endl;
     A b(a);
     //situation 2
     cout<<endl<<"situation 2"<<endl;
     cout<<endl<<"当传递对象参数和返回对象值时要按值传递,所以是两次复制构造函数"<<endl;
     func(a);
     //situation 3
     cout<<endl<<"situation 3"<<endl;
     cout<<endl<<"当传递对象参数和返回对象值时要按值传递,所以是两次复制构造函数,第二次参数传递是用func函数中的临时变量来直接初始化这个新对象c"<<endl;
     A c = func(a);
     //situation 4
     cout<<endl<<"situation 4"<<endl;
     cout<<endl<<"当传递对象参数和返回对象值时要按值传递,所以是两次复制构造函数,第二次参数传递是先初始化一个临时对象,再用临时对象初始化新对象d"<<endl;
     A d;
     d = func(a);
    //situation 5
    cout<<endl<<"situation 5"<<endl;
    cout<<endl<<"新建对象e,用a来初始化e"<<endl;
    A e;
    e = a;

   cout<<endl<<"The main is terminated~~"<<endl;
}

结果如下

得出的结论:

1.可以返回局部变量,但是不能返回局部变量的引用。

2.在return a 时并不是简单的返回这个局部变量,而是返回的是a的一个副本temp,
temp是通过拷贝构造函数实现的 即 A temp(a);也就是说在主函数中用的其实是个temp,而a早在func调用完毕后就释放了,而temp这个对象 暂时在主函数中贮存。
3.temp只是临时构造的,在赋值完毕之后它就析构了,不是一直在主函数中贮存的。
4.在拷贝构造这种情况下,编译器都不再产生副本,而赋值还是不行的。

///@@@@@@@@

///下面的程序说明,代码可以被优化,以来使用或者不使用复制构造函数

#include <string>
#include <iostream>
using namespace std;

class GarCat
{
public:
    GarCat(void);
    GarCat(int age,int weight);
    GarCat(const GarCat & rhs);
    GarCat & operator=(const GarCat &hs);
    int GetAge(){return itsAge;}
    int GetWeight(){return itsWeight;}
    void SetAge(int age){ itsAge = age;}
private:
    int itsAge;
    int itsWeight;
    //int itsAge = 5;//error
    //只有静态常量数据成员才能在类中初始化
public:
    virtual ~GarCat(void);
};

GarCat::GarCat(void)
{
    cout<<"GarCat is constructing..."<<endl;
}

GarCat::GarCat(int age,int weight)
{
    cout<<"GarCat is constructing..."<<endl;
    itsWeight = weight;
    itsAge = age;
}

GarCat::GarCat(const GarCat & rhs)
{
    cout<<"GarCat copy is constructing..."<<endl;
    cout<<this<<" from "<<&rhs<<"copy."<<endl;
    itsWeight = rhs.itsWeight;
    itsAge = rhs.itsAge;
}

GarCat & GarCat::operator=(const GarCat & rhs)
{
    cout<<"GarCat  overloaded..."<<endl;
    cout<<" from "<<&rhs<<"get value."<<endl;
    itsAge = rhs.itsAge;
    itsWeight = rhs.itsWeight;
    return *this;
}

GarCat::~GarCat(void)
{
    cout<<"GarCat is deconstructing..."<<endl;
}

GarCat& func1();
GarCat func2();
GarCat& func3();
void func4(GarCat garcat);

int main()
{
    GarCat mycat;
    //这个值区别于java,不是赋初值为0,而是随机数
    cout<<mycat.GetAge()<<endl;
    //错误,这个是私有变量
    //cout<<mycat.itsAge<<endl;

    cout<<"-----------------------潇洒的分割线----------------------"<<endl;
    GarCat &rcat = func1();
    int age = rcat.GetAge();
    cout<<"rcat is "<<age<<"years old."<<endl;
    cout<<"&rcat is "<<&rcat<<endl;
    GarCat *pcat = &rcat;
    cout<<"pcat is "<<pcat<<endl;
    //delete rcat;//不能对引用使用delete
    //
    ///在没有new的时候是不能delete的
    ///所以下面delete的操作是错误的
    /
    delete pcat;
    //delete好像没有释放内存,怎么获取的还是原来的值
    //可能这个内存区域存放的还是原来的?
    //先new string 后再调用也没有变,与编译器有关还是什么?

    cout<<"没有新建string之前"<<rcat.GetAge()<<endl;
    cout<<"没有新建string之前"<<rcat.GetWeight()<<endl;
    for(int i=0;i<10;i++)
    {
        string *s =  new string("1234567890");
    }
    cout<<"新建string之后"<<rcat.GetAge()<<endl;
    cout<<"新建string之后"<<rcat.GetWeight()<<endl;

    //这是问题来了,rcat.getAge()为123了
    GarCat *pcat2 = new GarCat(123,444);
    cout<<"pcat2 is "<<pcat<<endl;
    cout<<"delete pcat后使用rcat会发生什么问题??"<<endl;
    cout<<"delete pCat后 &rCat = "<<&rcat<<endl;
    cout<<"delete pCat后 rCat.age = "<<rcat.GetAge()<<endl;
    cout<<"delete pCat后 rCat.weight = "<<rcat.GetWeight()<<endl;
    cout<<"-----------------------潇洒的分割线----------------------"<<endl;
    GarCat mycat2 = func1();
    cout<<"myCat2的地址是 "<<&mycat2<<endl;
    cout<<endl<<"-------------------------------------------------------"<<endl;


    GarCat mycat5;
    func4(mycat5);

    return 0;

}

GarCat & func1()
{
    GarCat *pgarfield = new GarCat(5,9);
    cout<<  "pgarfield: "<<  pgarfield <<endl;
    return *pgarfield;
}

GarCat func2()
{
    GarCat tmp;
    cout<<"int the func2 tmp pointer "<<&tmp<<endl;
    tmp.SetAge(9999);
    return tmp;
}

/*
GarCat &func3()
{
    GarCat tmp;
    cout<<"int the func3 tmp pointer "<<&tmp<<endl;
    tmp.SetAge(9999);
    return tmp;
}
*/

void func4(GarCat garcat)
{
    cout<<"形式参数garcat的地址"<<&garcat<<endl;
}

先说说第一块吧,
这个貌似应该调用拷贝构造的地方没有调用拷贝构造,应该是编译器做的优化,可以参考《深入探索C++对象模型》P66页。
按照书中的说法,很可能只创建了1个对象
 
X bar()
{
    X xx;
    return xx;
}
可能会被编译器优化成
void bar(X & _result)
{
    _result.X::X();
    return;
}
 
所以调用X a = bar(),其实被转换成 bar(X& a);
所以一个本该调用拷贝构造的地方却很可能调仅用了构造函数来完成工作。
这个问题我也没有搞明白,我没有试过lz的代码是不是会这样,如果真这样的话,我觉得编译器就管的太多了,如果拷贝构造中有一些特殊的功能呢(就像楼主有个输出语句),岂不是无声无息中被抹杀了。这个功能被称为NRV,好像一直没有人对这个问题给出非常明确的回答
------------------------------------------------------------------------------------------------------------------------------
thx,在vs2005中代码优化开启时,第一块确实只会调一个构造函数
把代码优化禁用后,就会调用拷贝构造函数了
确实是个NRV的问题
http://blog.vckbase.com/bruceteen/archive/2005/12/30/16652.html
------------------------------------------------------------------------------------------------------------------------------

正如4楼的所说~~~
到底创建多少临时对象,要视编译器而定,看编译器是否采用NVR...
(其实一般情况下,NVR都是未采用的~),因此对后三个注释代码做
如下分析:写出编译后的伪代码(仅供参考)
编译器更改后的函数原型void TheFunctionTwo(SimpleCat&)
注释1:
    SimpleCat myCat3;
    TheFunctionTwo(myCat3)
   {
      SimpleCat tempCat; 
      templCat.SimpleCat::SimpleCat();
      cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl; 
      tempCat.SetAge(9999);
      myCat3.SimpleCat::SimpleCat(tempCat);
      tempCat.SimpleCat::~SimpleCat();
   }
注释2:
    SimpleCat myCat4;
    myCat4.SimpleCat::Simple();
    SimpleCat temp;
    TheFunctionTwo(temp)
   {
     SimpleCat tempCat; 
      templCat.SimpleCat::SimpleCat();
      cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl; 
      tempCat.SetAge(9999);
      temp.SimpleCat::SimpleCat(tempCat);
      tempCat.SimpleCat::~SimpleCat();
   }
    myCat4.operator=(temp);
   temp.SimpleCat::~SimpleCat();
注释3: 
    //SimpleCat &rmyCat = TheFunctionTwo(); 
     SimpleCat temp;
     SimpleCat &rmyCat=temp;
    TheFunctionTwo(temp)
    {
      SimpleCat tempCat; 
      templCat.SimpleCat::SimpleCat();
      cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl; 
      tempCat.SetAge(9999);
      temp.SimpleCat::SimpleCat(tempCat);
      tempCat.SimpleCat::~SimpleCat();
   }
这样就知道到底需要多少临时对象了~~~~

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
赋值构造函数(Assignment Constructor)是在C++中一个特殊的构造函数。当一个对象已经被创建后,我们可以通过赋值操作符(=)将另一个已存在的对象的值赋给它。赋值构造函数被用来定义这个赋值操作符的行为。 赋值构造函数的定义形式和普通的构造函数类似,但是它只有一个参数,即对同类对象的引用。通常,赋值构造函数的参数是一个const引用,因为我们只需要读取被赋值对象的值而不需要修改它。通过这个参数,我们可以在赋值构造函数内部访问和复制被引用对象的成员。 在编写一个赋值构造函数时,我们需要遵循三个重要的规则: 1. 分配内存:如果新对象需要通过动态内存分配来存储数据,我们需要在赋值构造函数中显式地进行内存分配。 2. 复制数据:赋值构造函数应该将被引用对象的数据复制给新对象的成员变量。这可以通过逐个成员变量赋值操作完成。 3. 避免资源泄漏:如果新对象赋值之前已经分配了内存或者占用了其他外部资源,我们需要在复制数据之前释放这些资源,以避免资源泄漏。 赋值构造函数通常会在对象赋值时自动调用,但我们也可以手动显式地调用赋值构造函数来完成赋值操作。 总结起来,赋值构造函数为我们提供了一种在对象赋值时进行自定义操作的方式。通过定义赋值构造函数,我们可以确保对象赋值时能够正确地复制数据,并处理内存和资源的释放,从而提高代码的可读性和可维护性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值