MST

星途 面试题库

面试题:C++ 中拷贝控制与多态性的复杂交互及解决方案

考虑一个具有继承体系的 C++ 类层次结构,基类中有虚函数,派生类有各自独特的成员变量。当进行对象的拷贝操作时,可能会遇到什么与多态性相关的问题?请举例说明,并阐述如何通过合理设计拷贝控制(拷贝构造函数、拷贝赋值运算符等)来解决这些问题,同时分析这种设计对程序性能和可维护性的影响。
27.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. 可能遇到的与多态性相关的问题

  • 切片问题:当通过基类对象拷贝派生类对象时,派生类中独有的成员变量会被截断,只保留基类部分的成员变量,导致信息丢失。例如:
#include <iostream>
class Base {
public:
    virtual void print() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
    int data;
    Derived(int d) : data(d) {}
    void print() override { std::cout << "Derived: " << data << std::endl; }
};
int main() {
    Derived d(10);
    Base b = d; // 切片,d的data成员被截断
    b.print(); // 输出Base,丢失了Derived类独有的数据和行为
    return 0;
}
  • 浅拷贝问题:如果使用默认的拷贝构造函数和拷贝赋值运算符,对于包含指针成员的类,可能会导致多个对象指向同一块内存,当一个对象析构释放内存后,其他对象的指针成为野指针,引发未定义行为。例如:
#include <iostream>
#include <cstring>
class Base {
public:
    char* str;
    Base(const char* s) {
        str = new char[strlen(s) + 1];
        std::strcpy(str, s);
    }
    ~Base() { delete[] str; }
};
class Derived : public Base {
public:
    int data;
    Derived(const char* s, int d) : Base(s), data(d) {}
};
int main() {
    Derived d1("Hello", 10);
    Derived d2 = d1; // 默认浅拷贝,d1和d2的str指向同一块内存
    d1.str[0] = 'X'; // 改变d1的str,d2的str也会改变
    return 0;
}

2. 解决方法

  • 深拷贝:在派生类中实现自定义的拷贝构造函数和拷贝赋值运算符,确保派生类对象的所有成员变量(包括基类的成员变量)都被正确拷贝。例如:
#include <iostream>
#include <cstring>
class Base {
public:
    char* str;
    Base(const char* s) {
        str = new char[strlen(s) + 1];
        std::strcpy(str, s);
    }
    Base(const Base& other) {
        str = new char[strlen(other.str) + 1];
        std::strcpy(str, other.str);
    }
    Base& operator=(const Base& other) {
        if (this != &other) {
            delete[] str;
            str = new char[strlen(other.str) + 1];
            std::strcpy(str, other.str);
        }
        return *this;
    }
    ~Base() { delete[] str; }
};
class Derived : public Base {
public:
    int data;
    Derived(const char* s, int d) : Base(s), data(d) {}
    Derived(const Derived& other) : Base(other), data(other.data) {}
    Derived& operator=(const Derived& other) {
        if (this != &other) {
            Base::operator=(other);
            data = other.data;
        }
        return *this;
    }
};
int main() {
    Derived d1("Hello", 10);
    Derived d2 = d1;
    d1.str[0] = 'X';
    std::cout << "d1: " << d1.str << ", " << d1.data << std::endl;
    std::cout << "d2: " << d2.str << ", " << d2.data << std::endl;
    return 0;
}
  • 使用虚拷贝构造函数:在基类中定义一个虚的克隆函数,派生类重写该函数,通过该函数实现多态的拷贝。例如:
#include <iostream>
class Base {
public:
    virtual Base* clone() const = 0;
    virtual void print() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
    int data;
    Derived(int d) : data(d) {}
    Derived* clone() const override { return new Derived(*this); }
    void print() override { std::cout << "Derived: " << data << std::endl; }
};
int main() {
    Base* b1 = new Derived(10);
    Base* b2 = b1->clone();
    b2->print();
    delete b1;
    delete b2;
    return 0;
}

3. 对程序性能和可维护性的影响

  • 性能影响
    • 深拷贝:由于需要额外分配内存并复制数据,深拷贝会带来一定的性能开销,特别是当对象包含大量数据或复杂数据结构时。
    • 虚拷贝构造函数:使用虚函数表进行函数调用会有一些间接开销,同时动态内存分配(new操作)也有一定的性能损耗。
  • 可维护性影响
    • 深拷贝:通过显式定义拷贝控制成员,使得代码逻辑更加清晰,其他开发人员能够清楚地了解对象拷贝时的行为,提高了代码的可维护性。但是,代码量增加,需要更多的维护工作。
    • 虚拷贝构造函数:这种设计遵循了多态性原则,使得对象的拷贝操作更加灵活,对于复杂的继承体系,更易于扩展和维护。但由于涉及虚函数机制和动态内存管理,可能对初学者理解和调试代码带来一定难度。