C++ 传值,传引用和传指针-参考Effective C++ 第三版Item20~21

  1.  拷贝构造函数 copy construtor 
    • 为什么有copy construtor?
      函数的传参数有三种方式:传值,传引用和传指针。传值会产生对象的副本,而后两者是传的是对象的别名和对象的指针。
      问题来了: 如果传值方式一个比较大的对象时候,在产生对象副本的时候一般会触发对象的构造函数。
      一般情况下构造函数会完成一些初始化工作,但是很有可能传进来的对象已经被改变了,如果调用构造函数的话这些对象的属性又恢复到初始状态了,这不是我们想要的。
      所以这种情况下其实调用的拷贝构造函数(如果不重写调用的是系统默认的拷贝构造函数)。 
    • 拷贝构造函数的功能就是把对象变量的值(包括指针的值,这里就可能出现问题)付给新的对象,而不去调用构造函数。
      在函数执行时候其实操作的时候对象的副本,在函数调用结束时候,会调用对象的析构函数,析构对象的副本。
    • 传值的问题
      如果在传对象的值的时候,对象中的某个变量是指针,那么拷贝构造函数在对象的副本的指针的值和对象的是一样的,指向的是一块内存。被调用函数退出时,对象的析构函数中会调用对这块内存的析构,这时候虽然析构的时候副本的指针但是释放的内存还是原来对象的内存。 如果主程序中不在使用对象的指针可能不会出现问题,但是问题在于如果主程序中这个对象析构的时候 这个指针已经是空了,在析构会出现严重的错误。
      另外在函数返回对象时候也会出现这个问题。
    • 解决办法:
      传引用或者传指针。
      如果真的想传对象,因为不想在函数内部改对象的值,可以自己定义拷贝构造函数,自己重新申请一块内存给指针,并作相应的保护。
    • 拷贝构造函数被调用的是三种情况:
      • 一个对象以值传递的方式传入函数体
      • 一个对象以值传递的方式从函数返回
      • 一个对象需要通过另外一个对象进行初始化
    • 如果不用拷贝构造函数,前两种情况会出现指针为空的错误;第三种是初始化和赋值不同的原因。
    • 当函数返回时,其实我们得到的是原对象的一份拷贝。

  2. 注意事项:
    拷贝构造函数会调用基类的拷贝构造函数(有点疑问,因为我的程序它调用了基类的构造函数,很奇怪)和成员函数
    • 例子程序一

      class CPerson
      {
      public:
           CPerson(void)
           {
                cout<<"creat a person @"<<this<<endl;
           }
           ~CPerson(void)
           {
                cout<<"delete a person @"<<this<<endl;
           }
           CPerson(CPersion& a)
           {
                cout<<"Copy constuction func in Person @"<<this<<endl;
           }
      };

      class CStudent : public CPersion
      {
      public:
           CStudent(void)
           {
                cout<<"creat a student @"<< this <<endl;
           }
           ~CStudent(void)
           {
                cout<<"Copy constuction func in Student @"<< this <<endl;
           }
           CStudent(CStudent& a):CPersion(a)
           {
                cout<<"delete a student @"<< this <<endl;
           }
      };

      bool IsAStudent(CStudent s)
      {
           cout<< "I am @"<< &s << endl;
           return false;
      }

      int _tmain(int argc, _TCHAR* argv[])
      {
           CStudent s = CStudent();
           IsAStudent(s);

           getchar();
           return 0;
      }

      输出是
      creat a person @003AF657       // 生成Student对象调用的基类Person 构造函数
      creat a student @003AF657      // 生成Student对象调用构造函数
      creat a person @003AF56C       // 调用传student对象的函数时候 调用Persion的 构造函数 ***为什么没有调用Persion的拷贝构造函数呢****已经解决了
      Copy constuction func in Student @003AF56C // 调用的时候Student的拷贝构造函数
      I am @003AF56C
      delete a student @003AF56C    // 析构传值函数的基类对象
      delete a person @003AF56C     // 析构传值函数的student对象
      delete a student @003AF657    // 调用函数中对象析构
      delete a person @003AF657     // 调用函数中基类的析构
    • 上面的疑问是这样的,因为我自己定义了派生类的拷贝构造函数,但是这个函数没有去调用基类的拷贝构造函数,所以会出现调用了基类的构造函数,如果我定义成下面这样
      CStudent(CStudent& a):CPersion(a)
           {
                cout<<"delete a student @"<< this <<endl;
           }
      输出就是如下
      creat a person @0019FE0B
      creat a student @0019FE0B
      Copy constuction func in Persion @0019FD20
      Copy constuction func in Student @0019FD20
      Who am I @0019FD20
      delete a student @0019FD20
      delete a person @0019FD20
      delete a student @0019FE0B
      delete a person @0019FE0B
      多谢  http://my.csdn.net/Adol1111 的解答

  3. 传值,传引用和传指针
    • 从上面的问题可以看到:传值的效率不高,所以我们可以尽可能的使用传引用或者传值,
      即将bool IsAStudent(CStudent& s) 这样的输出是
      creat a person @0037FE1B
      creat a student @0037FE1B
      Who am I @0037FE1B
      delete a student @0037FE1B
      delete a person @0037FE1B
    • 两外按Effective C++ version 3, Item 20所说,还会出现slicing的问题。 但是我没有看懂 希望有人能帮解释下。
    • 另外对于内置类型(如int,bool, 不包含string)以及STL的迭代器和函数对象,对他们往往传值更巧当。
  4. Effective C++ v3 Item 21, 不要尝试在Operator* 中返回一个引用
    • 问题是这样的 
      class Retional{
      public:
           Reational(int numerator = 0, int denominator = 1);
           ....
           
      private:
           int n,d;
           
           friend const Reational operator*(const Rational& lhs, const Rational& rhs);
      }

      如果以传引用返回则函数应该被定义为
      const Reational& operator*(const Rational& lhs, const Rational& rhs)
      {
           Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
           return result;
      }
      这个方法的问题在于返回的reference 指向是local的对象,而这个local的对象在函数退出前销毁了。
      同理如果返回一个指针一样会有这样的问题。

      另外考虑在heap内构造一个对象,返回refence指向它
      const Reational& operator*(const Rational& lhs, const Rational& rhs)
      {
           Rational* result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
           return *result;
      }
      问题是 谁来释放new的内存。
      即使调用一次释放一次。
      但是遇到 连乘
      Rational x, y, z, w;
      w = x * y * z;
      两次new 需要两次delete,很容易内存泄露

      假如使用static Rational对象 
      const Reational& operator*(const Rational& lhs, const Rational& rhs)
      {
           static Rational result;
           result = Rational(lhs.n*rhs.n, lhs.d*rhs.d);
           return result;
      }

      那么以后的问题更严重例如
      bool operator==(const Rational& lhs, const Rational& rhs);
      Rational x, y, z, w;
      if((x*y) == (z*w))
      {
           // .....
      }else 
      {
           // ......
      }
      我们永远得到是相等的情况, 而且这种方法线程不安全。

      正确的办法:该返回一个对象的时候就返回一个对象。
      inline const Rational operator*(const Rational& lhs, const Rational& rhs)
      {
           return Rational(lhs.n*rhs.n, lhs.d*rhs.d);
      }
  5. 所以总结
    • 传值的效率不高,所以我们可以尽可能的使用传引用或者传值,(除了内置类型,STL迭代器)
    • 拷贝构造函数可以减少传值的默写问题,
    • 绝不要返回pointer 或者reference指向local stack的对象,或者返回reference指向heap-allocated的对象。
    • 在operator* 不要尝试返回&, 就直接返回一个对象吧,虽然要构造和析构 但总比用&引起的问题好。
      inline const Rational operator*(const Rational& lhs, const Rational& rhs)
      {
           return Rational(lhs.n*rhs.n, lhs.d*rhs.d);
      }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值