C++ 编程技巧与规范(二)

2020-11-19  本文已影响0人  e196efe3d7df

拷贝构造函数和拷贝复制运算符的书写

如下:

namespace demo2 {
    class A {
    public:
        A():m_i(0), m_j(0) {}
        A(int i, int j):m_i(i), m_j(j) {}

        A(const A& temp) {
            m_i = temp.m_i;
            m_j = temp.m_j;
        }

        A& operator=(const A& temp)
        {
            m_i = temp.m_i;
            m_j = temp.m_j;
            return *this;
        }

    public:
        int m_i;
        int m_j;
    };
}

int main()
{
    demo2::A a1;
    demo2::A a2 = a1;   //拷贝构造
    demo2::A a3;
    a3 = a1;            //拷贝赋值
}

对象自我赋值产生的问题

对象的自我赋值,会产生一些问题,比如:

#pragma warning(disable:4996) //strcpy会触发4996警告,头部加入 #pragma warning(disable:4996) 即可
namespace demo3 {
    class A {
    public:
        A() :m_i(0), m_j(0), m_charPtr(new char[100]){}
        A(int i, int j) :m_i(i), m_j(j), m_charPtr(new char[100]) {}

        A(const A& temp) {
            m_charPtr = new char[100];
            memcpy(m_charPtr, temp.m_charPtr, 100);
            m_i = temp.m_i;
            m_j = temp.m_j;
        }

        A& operator=(const A& temp)
        {
            delete m_charPtr;

            m_charPtr = new char[100];
            memcpy(m_charPtr, temp.m_charPtr, 100);
            m_i = temp.m_i;
            m_j = temp.m_j;
            return *this;
        }

    public:
        int m_i;
        int m_j;
        char* m_charPtr;
    };
}

int main()
{
    demo3::A a;
    strcpy(a.m_charPtr, "abcdef"); //strcpy会触发4996警告,头部加入 #pragma warning(disable:4996) 即可
    a = a;
}

A的赋值运算符重载函数,会先清理自身的m_charPtr,然后重新分配内存,把目标的m_charPtr赋值给自身的m_charPtr。当自我赋值时,会目标对象和自身对象是同一块内存,所以清理后,数据丢失,无法进行复制。可以进行如下改动

  1. 如果是同一对象的自我赋值,则直接返回自身。
A& operator=(const A& temp)
{
    if (this == &temp)
    {
        return *this;
    }

    delete m_charPtr;

    m_charPtr = new char[100];
    memcpy(m_charPtr, temp.m_charPtr, 100);
    m_i = temp.m_i;
    m_j = temp.m_j;
    return *this;
}
  1. 先把目标对象的资源复制一份,然后用复制的资源进行赋值。
A& operator=(const A& temp)
{
    char* tempCharPtr = new char[100];
    memcpy(tempCharPtr, temp.m_charPtr, 100);

    delete m_charPtr;

    m_charPtr = tempCharPtr;
    m_i = temp.m_i;
    m_j = temp.m_j;
    return *this;
}

第一种,简单明了。第二种,效率可能会更高

继承关系下拷贝构造函数和拷贝复制运算符的书写

如果在子类中没有定义自己的拷贝构造函数和拷贝复制运算符函数,则编译器会调用父类的拷贝构造函数和拷贝复制运算符函数。
反之,如果子类中定义自己的拷贝构造函数和拷贝复制运算符函数,则编译器会调用子类字自身的拷贝构造函数和拷贝复制运算符函数,不再调用父类的拷贝构造函数和拷贝复制运算符函数,需要程序员自己去调用父类的拷贝构造函数和拷贝复制运算符函数。如下:

namespace demo4 {
    class A {
    public:
        A() :m_i(0), m_j(0), m_charPtr(new char[100]) {}
        A(int i, int j) :m_i(i), m_j(j), m_charPtr(new char[100]) {}

        A(const A& temp) {
            m_charPtr = new char[100];
            memcpy(m_charPtr, temp.m_charPtr, 100);
            m_i = temp.m_i;
            m_j = temp.m_j;
        }

        A& operator=(const A& temp)
        {
            char* tempCharPtr = new char[100];
            memcpy(tempCharPtr, temp.m_charPtr, 100);

            delete m_charPtr;

            m_charPtr = tempCharPtr;
            m_i = temp.m_i;
            m_j = temp.m_j;
            return *this;
        }
        
        virtual ~A() {}

    public:
        int m_i;
        int m_j;
        char* m_charPtr;
    };

    class B :public A {
    public:
        B() {}
        ~B(){}

        B(const B& temp) :A(temp) {
            //A(temp) 不能将调用父类复制构造函数放在此处,会造成二义性,编译错误,编译器会解析成 A temp;
        }

        B& operator=(const B& temp) {
            A::operator=(temp);
            return *this;
        }       
    };
}

int main()
{
    demo4::B b1;
    demo4::B b2 = b1;   //拷贝构造函数
    b2 = b1;            //拷贝赋值运算
}

拷贝构造函数和拷贝复制运算符中重复代码的处理

不建议拷贝构造函数和拷贝复制运算符函数互相调用

上一篇下一篇

猜你喜欢

热点阅读