MST

星途 面试题库

面试题:C++ 赋值运算符与拷贝构造函数在复杂场景下的行为分析

在多继承和虚继承的复杂继承体系下,分析赋值运算符和拷贝构造函数在对象复制过程中的调用顺序、作用及可能引发的问题,若存在菱形继承结构,如何通过合理设计赋值运算符和拷贝构造函数来避免数据重复和内存管理问题,给出详细的设计思路和示例代码。
35.5万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. 调用顺序、作用及可能引发的问题

  • 调用顺序
    • 拷贝构造函数:当用一个已存在对象创建新对象时调用。在多继承和虚继承体系下,先调用基类(包括虚基类)的拷贝构造函数,顺序按照继承列表中声明的顺序,然后调用自身类的拷贝构造函数。
    • 赋值运算符:当对已存在对象进行赋值操作时调用。先调用目标对象的析构函数释放原有资源(如果有),然后调用赋值运算符函数,同样先处理基类(包括虚基类)的赋值,再处理自身类的赋值。
  • 作用
    • 拷贝构造函数:用于创建一个与已有对象内容相同的新对象,它负责初始化新对象的成员变量,包括从基类继承而来的成员变量。
    • 赋值运算符:用于将一个已存在对象的内容赋值给另一个已存在对象,在赋值过程中需要正确处理资源的释放和重新分配,以保证对象状态的一致性。
  • 可能引发的问题
    • 数据重复:在菱形继承结构中,如果不使用虚继承,可能会导致从公共基类继承的成员变量在派生类中出现多份拷贝,造成数据冗余。
    • 内存管理问题:如果对象包含动态分配的资源(如指针指向的内存),不正确的拷贝构造函数和赋值运算符可能导致内存泄漏(如没有释放原有资源就重新分配)或悬空指针(如释放资源后未将指针置空)。

2. 菱形继承结构下的设计思路

  • 虚继承:使用虚继承确保公共基类在最终派生类中只有一份拷贝。这样在赋值和拷贝构造时,不会出现数据重复的问题。
  • 合理实现拷贝构造函数
    • 首先调用虚基类的拷贝构造函数,确保虚基类部分被正确初始化。
    • 然后按照继承列表顺序调用非虚基类的拷贝构造函数。
    • 最后初始化自身类的成员变量。
  • 合理实现赋值运算符
    • 首先检查是否是自赋值,如果是则直接返回。
    • 调用虚基类的赋值运算符进行虚基类部分的赋值。
    • 按照继承列表顺序调用非虚基类的赋值运算符。
    • 释放自身类动态分配的资源(如果有),并重新分配资源进行赋值。

3. 示例代码

#include <iostream>
#include <cstring>

class Base {
public:
    int value;
    Base(int v = 0) : value(v) {}
    Base(const Base& other) : value(other.value) {}
    Base& operator=(const Base& other) {
        if (this != &other) {
            value = other.value;
        }
        return *this;
    }
};

class Derived1 : virtual public Base {
public:
    Derived1(int v = 0) : Base(v) {}
    Derived1(const Derived1& other) : Base(other) {}
    Derived1& operator=(const Derived1& other) {
        if (this != &other) {
            Base::operator=(other);
        }
        return *this;
    }
};

class Derived2 : virtual public Base {
public:
    Derived2(int v = 0) : Base(v) {}
    Derived2(const Derived2& other) : Base(other) {}
    Derived2& operator=(const Derived2& other) {
        if (this != &other) {
            Base::operator=(other);
        }
        return *this;
    }
};

class FinalDerived : public Derived1, public Derived2 {
public:
    char* str;
    FinalDerived(int v = 0, const char* s = "") : Derived1(v), Derived2(v) {
        str = new char[strlen(s) + 1];
        strcpy(str, s);
    }
    FinalDerived(const FinalDerived& other) : Derived1(other), Derived2(other) {
        str = new char[strlen(other.str) + 1];
        strcpy(str, other.str);
    }
    FinalDerived& operator=(const FinalDerived& other) {
        if (this != &other) {
            Derived1::operator=(other);
            Derived2::operator=(other);
            delete[] str;
            str = new char[strlen(other.str) + 1];
            strcpy(str, other.str);
        }
        return *this;
    }
    ~FinalDerived() {
        delete[] str;
    }
};

int main() {
    FinalDerived obj1(10, "Hello");
    FinalDerived obj2(obj1);
    FinalDerived obj3;
    obj3 = obj1;
    return 0;
}

在上述代码中,通过虚继承 Base 类,避免了在 FinalDerived 类中 Base 类成员变量的重复。同时,合理实现了拷贝构造函数和赋值运算符,确保了对象复制过程中的数据一致性和正确的内存管理。