C++构造函数浅拷贝与深拷备

在很多的程序场景都会用到浅拷备或深拷备。然由于两种拷备方式的机制不同,所以应该场景也不同。如果错用会带来一定的程序风险。

浅拷备特点: 在拷备时把所有的值直接从源对象到目标对象上。

正是由于这种拷备数据的机制,当对象里的数据都是直接分配内存,不需要在堆栈上再分配内存,这种下可以直接使用浅拷备方式即可。

设想,如果一个对象中有指针变量,那么这个指针变量使用时,必然需要动态申请一块内存区域来存放数据。如果使用浅拷备机制,在拷备过程中指针变量的赋值也是直接的,那么这时源对象指针获得的数据是目标对象在堆栈上申请的某个内存区域首地址。拷备源对象的指针变量与目标对象指针指向同一个内存区域。此时有一个对象方法操作了这个指针变量内存数据,那么另外一个对象数据也同步影响。无法保证程序的一致性。

class Copy{
    
    
    public:
        Copy(const char* str, int var);
        ~Copy();

        void showInfo() const {
    
    cout << "Show name:" << name << endl;}
        void setInfo(const char* str){
    
    strcpy(name, str);};


    private:
        char* name; //指针使用申请内存。指针变量指向内存首地址
        int age;
}; /* end class declared */

Copy fp("tudou", 32);
Copy sp(fp); //或 Copy sp = fp;

此时 fp.name 指针变量存放的是申请内存空间的地址。赋值操作或浅拷备之后
sp.name = fp.name 即sp.name获得的值也申请的堆栈内存地址。指向同一内空间。

Demo示例验证

#include <iostream>
#include <string>
#include <string.h>
#include <thread>

using namespace std;

namespace zj001{
    
    

class Copy{
    
    
    public:
        Copy(const char* str, int var);
        ~Copy();

        void showInfo() const {
    
    cout << "Show name:" << name << endl;}
        void setInfo(const char* str){
    
    strcpy(name, str);};


    private:
        char* name;
        int age;
}; /* end class declared */

Copy::~Copy()
{
    
    
    //delete[] name; 
    //name = nullptr; 
    cout << "Destry addr:" << static_cast<void*>(name) << endl;
}

Copy::Copy(const char* str, int var)
{
    
    
    if(str){
    
    
        name = new char[strlen(str) + 1];
        strcpy(name, str);
    }
    else{
    
    
        name = new char[1];
        *name = '\0';
    }
    age = var;
}

} /* end namespace zj001 */


using namespace zj001;

void printClassInfo(Copy cp)
{
    
    
    const char* n = "pname";
    cp.setInfo(n);
    cp.showInfo();
}

int main(int argc, char** argv)
{
    
    
    Copy fp("tudou", 32);
    Copy sp(fp);

    fp.showInfo();
	sp.showInfo();

    printClassInfo(fp);
    puts("-------------------------------");
    fp.showInfo();

    return 0;
}

程序中进行三次的对象实例化,这里打印出了每一个实例对象name变量本身内存地址。
fp,sp都是直接实例对象,同时调用函数把copy类作为函数参考进行传参,这样在执行函数时会临时生成对象cp。在程序结果时可以看到执行了三次析构函数。
分别是:

打印name指向的内存空间值
Show name:tudou //fp.name 指针变量指向内存空间的值。
Show name:tudou //sp.name 指针变量指向内存空间的值。
Show name:pname //函数临时生成的实例 cp.name 指针变量指向内存空间的值被修改。
Destry addr:0x56536d117e70 //函数执行完成后释放的内存地址
-------------------------------
Show name:pname //在函数中cp.name修改值后,sp对象打印值即为修改后的
Destry addr:0x56536d117e70 // sp.name 指向的内存地址
Destry addr:0x56536d117e70 // fp.name 指向的内存地址

在程序执行完释放的name地址都是(0x56536d117e70 ),说明这三个实例中name指向是同一片内存区域。程序对此区域进行了三次释放。
浅拷备主要二个问题(目前理解):

  • 一个对象修改数据会影响其它对象数据(对象指针都指向同一内存)
  • 析构函数中如果对指针申请空间释放,会进行多次的内存释放,带来程序的未知性。

C++ 默认构造拷备函数即浅拷备方式。

正由于以上原因,我们就会想让各对象自己的指针变量申请空间,然后再把数据拷备过去。这样每一个对象都维护一份自己指向的内存空间,释放的也是自己指向内存空间。可以规避浅拷备导致的问题,这种由对象自己在构造时申请空间,自己维护,自己释放的方式叫深拷备。

这里使用了2个独立的命名空间来隔开拷备方式的实现代码

#include <iostream>
#include <string>
#include <string.h>
#include <thread>

using namespace std;

namespace zj001{
    
    

class Copy{
    
    
    public:
        Copy(const char* str, int var);
        ~Copy();

        void showInfo() const {
    
    cout << "Show name:" << name << endl;}
        void setInfo(const char* str){
    
    strcpy(name, str);};

    private:
        char* name;
        int age;
}; /* end class declared */

Copy::~Copy()
{
    
    
    //delete[] name; 
    //name = nullptr; 
    cout << "Destry addr:" << static_cast<void*>(name) << endl;
}

Copy::Copy(const char* str, int var)
{
    
    
    if(str){
    
    
        name = new char[strlen(str) + 1];
        strcpy(name, str);
    }
    else{
    
    
        name = new char[1];
        *name = '\0';
    }
    age = var;
}

} /* end namespace zj001 */


namespace zj002{
    
    

class Copy{
    
    
    public:
        Copy(const char* str, int var);
        Copy(const Copy& cp);
        ~Copy();

        void showInfo() const {
    
    cout << "Show name:" << name << endl;}
        void setInfo(const char* str){
    
    strcpy(name, str);};

    private:
        char* name;
        int age;
}; /* end class declared */

Copy::~Copy()
{
    
    
    //delete[] name; 
    //name = nullptr; 
    cout << "Destry addr:" << static_cast<void*>(name) << endl;
}

Copy::Copy(const char* str, int var)
{
    
    
    if(str){
    
    
        name = new char[strlen(str) + 1];
        strcpy(name, str);
    }
    else{
    
    
        name = new char[1];
        *name = '\0';
    }
    age = var;
}

Copy::Copy(const Copy& cp)
{
    
    
    name = new char[strlen(cp.name) + 1];
    strcpy(name, cp.name);
    age = cp.age;
}

} /* end namespace zj002 */

using namespace zj002;

void printClassInfo(Copy cp)
{
    
    
    const char* n = "pname";
    cp.setInfo(n);
    cp.showInfo();
}

int main(int argc, char** argv)
{
    
    
    Copy fp("tudou", 32);
    //Copy sp(fp);
    Copy sp = fp;

    fp.showInfo();
    sp.showInfo();

    printClassInfo(fp);
    puts("-------------------------------");
    fp.showInfo();

    return 0;
}

运行结果:

打印name指向的内存空间值
Show name:tudou //fp.name 指针变量指向内存空间的值。
Show name:tudou //sp.name 指针变量指向内存空间的值。
Show name:pname //函数临时生成的实例 cp.name 指针变量指向内存空间的值被修改。
Destry addr:0x561c7497a2c0 //函数执行完成后释放的内存地址
-------------------------------
Show name:pname // 在函数中cp.name修改值后,sp对象打印值不受影响
Destry addr:0x561c74979e90 // sp.name 指向的内存地址
Destry addr:0x561c74979e70 // fp.name 指向的内存地址

每个实例在释放obj.name地址时,三次释放都是不同内存空间地址,说明每个对象指针变量分别维护了一份各自的内存。各个对象实例操作时不受相互影响。同时执行完后释放的内存时也是安全的。

猜你喜欢

转载自blog.csdn.net/zj646268653/article/details/108664847