zoukankan      html  css  js  c++  java
  • 【转】拷贝构造函数/深拷贝/浅拷贝

    什么时候会用到拷贝构造函数?
     

    1-什么时候会用到拷贝构造函数? 
    2-什么时候有必要手动写拷贝构造函数?

    1-什么时候会用到拷贝构造函数?
               当任何你想复印东西的时候,而不管东西被复印成什么样子。即任何你想利用一个已有的类实例给另一个类实例赋值时,这种赋值可能是显式的,也可能是隐式的
    显式:classa_1=class_2;
    隐式:函数的形参有用到类对象却没有用引用或传址技术时
                   函数的返回值是一个对象也没有应用传址技术时
    2-什么时候有必要用拷贝构造函数?
    上述3种情况,如果没有涉及到深拷贝问题,就没有必要自己来编写拷贝构造函数,编译器有默认的可以很完美的完成任务
    还一种情况就是变态的拷贝:在拷内过程中进行缩放,放大等处理,不过没什么用

    //关于深拷贝以下是引用:traceback:http://www.netology.cn/blog/user1/bachelor/archives/2006/375.html

    关于深拷贝
    如果一个类中含有指针成员变量,则在利用一个已存在对象构造新的对象时,就会面临两种选择:深拷贝和浅拷贝。
           浅拷贝只是将对象间对应的指针成员变量进行简单的拷贝,即拷贝结束后新旧对象的指针指向相同的资源(指针的值是相同的);这种拷贝会导致对象的成员不可用,如下例:
            class Person
            {
               public :
               //....
               char * home;//the person's home
               void SetHome(char * str)
               {home = str;}
               ~Person()
                {
                  //...
                  delete [] home;
                }
            }
           //....
           char * place = new char [20];
           strcpy(place,"China");
           Person *A = new Person();
           A->SetHome(place);
       
           Person * B= Person(A);
           delete A;
          //....
          此时对象A 和对象B的成员home值相同,如果A对象 destroy,则对象B的成员home指向的地址变为不可用(对象A撤销时将home指向的资源释放了)。
           深拷贝是相对于浅拷贝而言的,为了避免上述情况的发生,将上例中的代码改造:对象拷贝时将指针指向的内容拷贝,代码如下:
          class Person
            {
               public :
               //....
               char * home;//the person's home
               void SetHome(char * str)
               {home = str;}
           
               Person & Person(const Person & per)
               {
                    //...
                    if(* this == per)//copy itself
            return *this;
                    home = new char[strlen(per.home) +1];//alloc new memory
           strcpy(home,per.home);
           return * this;
               }
               ~Person()
                {
                  //...
                  delete [] home;
                }
            }
         深拷贝之后,新旧对象的home成员指向的内容的值相同,而其自身的值不同。这样就可避免出现其中之一
         destroy 之后,另一对象的home成员不可用。
         但是,此种开辟新的内存区域然后赋值拷贝的方法在面临指针成员指向的数据量很大时出现效率低下的问题,因此又有下面的处理方法:为指针保留引用计数(reference counting)。
         当类每次拷贝这个指针时,就增加它的计数;对象destroy时检察指针的引用计数,如果为0就删除其指向的资源,否则计数减小。如果指针所指数据相对恒定是,指针引用计数尤为有效。而且在数据量庞大的时候,可以有较高的执行效率。
          类示例代码如下:

    //一个引用计数器很容易作为一个类来完成
    //用于带一个引用计数值得void指针的类
    class VoidRefPtr
    {
    public:
         //constructor allocates memory for data
         VoidRefPtr(void * pData,int nSize)
         {
          ptr = malloc(nSize);
          memcpy(ptr,pData,nSize);
          nCount = 1;
         }

         //increment the reference count
         VoidRefPtr * Ref()
         {
          nCount ++;
          return this;
         }

         //decrement the count and delete if zero
         void Derf()
         {
          if(!--nCount)
           delete this;
         }

         ~VoidRefPtr()
         {
          delete ptr
         }

         void * ptr()
         {
          return ptr
         }
    private:
         void * ptr;//the data
         unsigned int nCount;//the reference count

    };

    //这个类包含了一个实际的指针和用于对引用计数器自加自减的成员函数
    //当引用计数器减到零时,对象撤销自身。
    class RefcountCharPtr
    {
    public:
    RefcountCharPtr(const char * pData ="")
    {
         //constructor creates a ref pointer
         pRef = new VoidRefPtr((void *)pData,strlen(pData)+1);
    }

    RefcountCharPtr(const RefcountCharPtr &a)
    {
         //copy constructor increments ref count
         pRef = a.pRef->Ref();
    }

    ~RefcountCharPtr()
    {
         //destructor decrements ref count
         pRef ->Derf();
    }

    const RefcountCharPtr & operator = (const RefcountCharPtr & a)
    {
         //asignment
         //decrement ref count of existing pointer
         pRef->Derf();
         //increment that of assigned pointer
         pRef = a.pRef->Ref();
         return * this;
    }

    RefcountCharPtr & operator =(const char * pData)
    {
         //char string assignment
         //decrement ref count of existing pointer
         pRef->Derf();
         //make new pointer
         pRef = new VoidRefPtr((void *)pData,strlen(pData)+1);
         return *this;
    }

    //type conversion to a char string
    operator const char * ()
    {
         return (char * )pRef->ptr();
    }

    protected:
    private:
    VoidRefPtr * pRef;
    };
         一个同学给出了一个类似的实现:
          class RefData
    {
    public:
         RefData(char *s)
         {
          data = new String(s);
         }
    int refCount;
    String *data;
    };

    class MyString
    {
    public:
         MyString(const char *s = "");
    //...
    //...
    private:
         RefData * value; // 私有指针变量
    };

    MyString::MyString(const char *s = ""):value(new RefData(s))
    {
    }

    MyString & MyString::operator=(const MyString & str)
    {
    if (value == str.value) //如果是自己复制给自己
         return *this;
    if (--(value->refCount) == 0)
         delete value;
    value = s.value; // 只拷贝指针
    ++(value->refCount);
    return *this;
    }

    char & MyString::operator[](int dex) //改变值的时候
    {
    if (value->refCount >1)
    {
         --value->refCount;
         value = new RefData(value->data)
    }
    return value->data[index];
    }

    //测试示例
    int main()
    {
    MyString s1 ="hello";
    char *p = &s1[1];
    MyString s2 = s1;
    *p = 'x';
    }

         虽然引用计数的处理方法貌似不错,但是其亦存在其弊端:在多线程的条件下,其执行效率尤为低下。具体参见(Herb Shutter的在C++ Report杂志中的Guru专栏以及整理后出版的《More Exceptional C++》中专门的讨论)。
        
         最近在看设计模式的一些东东,偶然又发现了下面的一段:
         下面是一些可以使用Proxy模式的常见情况:
         ...
         4)智能指引(smart reference)--取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:
          对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为 smartpointers[Ede92]).
          当第一次引用一个持久对象时,将它装入内存。
          在访问一个实际对象前,检察是否已经锁定了它,以确保其他对象不能改变它。
          以上的叙述的用途好像有些像引用计数,但是具体如何,现在偶还不是很清楚,准备将两者放在一起,看其联系和区别究竟如何。
          以上只是最近在看c++时遇到的一点问题,将其放在一起讨论,希望可以明白其原理和实现。

  • 相关阅读:
    markdown转微信公众号编辑器
    微信公众号发布和群发的区别
    Win10自带HyperV虚拟机上安装Linux系统
    Sql优化的15个小技巧
    Md2All 简介 (公众号markdown排版)
    svg动态图
    Markdown的常用语法
    Md2All,把图片轻松上传到云图床,自动生成Markdown
    Postman 使用教程 API 接口自动化测试初探
    玩转公众号markdown排版
  • 原文地址:https://www.cnblogs.com/wengzilin/p/3038362.html
Copyright © 2011-2022 走看看