复制不完全和自我赋值问题

来源:互联网 发布:apk软件编写 编辑:程序博客网 时间:2024/05/01 18:00

复制不完全和自我赋值问题

          复制问题主要涉及的函数有两个:拷贝构造函数和赋值操作符。当我们不定义自己的这两个函数时,编译器就会为我们定义。但当我们自己定义时,编译器就会给出一些报复行为,即使我们定义的拷贝构造函数和赋值操作符不能按我们的心意去完成复制工作时,它也不会给出任何提示信息。所以,在自己定义这两个函数时,我们必须记住:复制要完全,不能遗漏掉任何一个成分。

          在有些程序中,这个问题不会太明显:

     class Test

     {

     private:

     string name;

     public:

     Test(string s=”hello”):name(s){}

     Test(const Test& t):name(t.name){}

     Test& operator=(const Test& t)

           {

                  name=t.name;

          }

     };

          当我们在主程序里调用

             Test t(“Right”);

             Test t1(t);

             Test t2;

             t2=t;

          这时我们的复制都没有问题,完全可以得到我们预想的结果,但如果类中还有类对象或者继承类的话,那么情况将会有很大的变化。这里给出一个演示程序,可以看出在继承类情况下,拷贝构造函数和赋值操作符的不同,同时还可以追踪程序的执行过程。

         #include <iostream>

         using namespace std;

         class Base

         {

              protected:

                      int i;

              public:

                     Base(int ii=0):i(ii)

                           {

                                cout<<"Base() called here!"<<endl;

                           }

                     Base(const Base& b):i(b.i)

                           {

                               cout<<"Base(const Base&) called here!"<<endl;

                           }

                     Base& operator=(const Base& b)

                           {

                                          i=b.i;

                                         cout<<"Base::operator() called here!"<<endl;

                                          return *this;

                          }

          };

          class Derived:public Base

         {

                   private:

                        int j;

                   public:

                        Derived(int jj=0):Base(jj),j(jj)

                           {

                                   cout<<"Derived(int) called here!"<<endl;

                           }

                       //这里的Base(d)是必不可少的,否则出现复制不完全

                      Derived(const Derived& d):Base(d),j(d.j)

                         {

                                 cout<<"Derived(const Derived&) called here!"<<endl;

                         }

                     Derived& operator=(const Derived& d)

                        {

                        //这里的Base::operator=(d)也必不可少,否则出现复制不完全

                               Base::operator=(d);

                               j=d.j;

                               cout<<"Derived::operator=() called here!"<<endl;

                               return *this;

                        }

                      //用于输出测试数据

                    void print()

                      {

                           cout<<"The data is "<<"i= "<<i<<" j= "<<j<<endl;

                       }

          };

         int main()

          {

                   Derived d(100);

                  d.print();

                  cout<<"**********copy***********"<<endl;

                  Derived d1(d);

                  d1.print();

                  cout<<"**********asignment**********"<<endl;

                   Derived d2;

                  d2=d;

                  d2.print();

                   return 0;

         }

          这里,Derived类的拷贝构造函数和赋值操作符在没有利用Base类的拷贝构造函数和赋值操作符时,继承过来的成员i的拷贝和赋值操作所得到的值都不会改变,因此存在复制的不完全问题。解决方法也如程序所示,复制要完全,不要漏掉任何一个成分,我们在Derived类中采用Base类的拷贝构造函数和赋值操作符对Base类进行复制,从而解决了复制不完全问题。

           这里,针对operator=必须提出另外一个问题,那就是自我赋值。

Base& operator=(const Base& b)

          我们传递的参数不一定非不同于*this,如果传递的是它自身的引用,那么将会出现一些莫名其妙的问题,特别是针对有删除操作的赋值操作符~

          class X{...};

          class Y 

           {

                private:

                        X* p;

                public:

                       Y& operator=(const Y& y)

           {

                      delete p;

                      p=new X*(y.p);

                      return *this;

           }

        };

           如果我们调用赋值操作符,而传递给它的是它自身,会有什么情况发生呢?首先p指向对象会被删除,然后利用new操作符建立一个新的指针并以被删除的对象初始化,这样就定义了一个不确定的行为。

           第一种解决方案是自我检测

         Y& operator=(const Y& y)

          {

                if(this!=&y)

                {

                      delete p;

                      p=new X*(y.p);

                 }

                 return *this;

          }

          每一次进行赋值时都先检测赋值对象是不是自身,如果是的话就直接返回。这样就不会出现上述未定义的行为。但存在另外一个问题,如果new操作符由于申请内存失败等导致抛出异常,因此在以后的程序中,你可能无法安全的删除这个对象,甚至不能正确读取。

          对自我检测技术进行改进的另一种方案是

               Y& operator=(const Y& y)

                 {

                          X* temp(p);

                        p=new X*(temp);

                        delete temp;

                        return *this; 

                }

          采用这种方案以后,如果new抛出异常的话,则原来的对象不会被改变,从而不会因为异常而导致难以察觉的未定义行为。

          当然,还有其他诸如采用标准swap的解决方案,请参考Effective C++内的相关主题。