MST

星途 面试题库

面试题:C++类缺省函数与内存管理及性能优化

在一个包含动态分配内存成员的C++类中,分析缺省拷贝构造函数和缺省赋值运算符可能带来的问题,特别是从内存管理和性能角度。如何正确实现自定义的拷贝构造函数和赋值运算符以避免这些问题?并且说明在现代C++(如C++11及以后)中,移动构造函数和移动赋值运算符如何与缺省函数相互配合,进一步优化性能。
49.9万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

缺省拷贝构造函数和缺省赋值运算符的问题

  1. 内存管理问题
    • 缺省拷贝构造函数和缺省赋值运算符执行的是浅拷贝。如果类中有动态分配的内存成员,当使用缺省拷贝构造函数创建新对象或使用缺省赋值运算符进行赋值时,多个对象会指向同一块动态分配的内存。当其中一个对象析构时,这块内存会被释放,导致其他对象指向已释放的内存,产生悬空指针,进而引发未定义行为。
    • 例如:
    class MyClass {
    private:
        int* data;
    public:
        MyClass(int value) {
            data = new int(value);
        }
        // 缺省拷贝构造函数和赋值运算符(隐式生成)
        ~MyClass() {
            delete data;
        }
    };
    
    int main() {
        MyClass a(10);
        MyClass b = a; // 使用缺省拷贝构造函数
        // 此时a和b的data指向同一块内存
        return 0;
        // 析构时,a先析构释放内存,b再析构,会导致double free错误
    }
    
  2. 性能问题
    • 缺省拷贝构造函数和缺省赋值运算符对于复杂对象可能会进行不必要的重复数据拷贝,尤其是当对象中包含大量数据时,这会导致性能下降。

自定义拷贝构造函数和赋值运算符

  1. 自定义拷贝构造函数
    • 实现深拷贝,为新对象分配独立的内存,并将源对象的数据复制到新分配的内存中。
    class MyClass {
    private:
        int* data;
    public:
        MyClass(int value) {
            data = new int(value);
        }
        MyClass(const MyClass& other) {
            data = new int(*other.data);
        }
        ~MyClass() {
            delete data;
        }
    };
    
  2. 自定义赋值运算符
    • 同样要实现深拷贝,同时要处理自我赋值的情况,避免释放自身内存后再使用已释放的内存。
    MyClass& MyClass::operator=(const MyClass& other) {
        if (this != &other) {
            delete data;
            data = new int(*other.data);
        }
        return *this;
    }
    

移动构造函数和移动赋值运算符与缺省函数的配合

  1. 移动构造函数
    • 在C++11及以后,移动构造函数允许在对象所有权转移时避免不必要的深拷贝,提高性能。移动构造函数接收一个右值引用参数,它窃取源对象的资源(如动态分配的内存),而不是进行深拷贝。
    class MyClass {
    private:
        int* data;
    public:
        MyClass(int value) {
            data = new int(value);
        }
        MyClass(const MyClass& other) {
            data = new int(*other.data);
        }
        MyClass(MyClass&& other) noexcept {
            data = other.data;
            other.data = nullptr;
        }
        ~MyClass() {
            delete data;
        }
    };
    
  2. 移动赋值运算符
    • 移动赋值运算符类似,处理右值引用参数,在转移资源的同时处理自我赋值情况。
    MyClass& MyClass::operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
    
  3. 与缺省函数的配合
    • 移动构造函数和移动赋值运算符与缺省的拷贝构造函数和赋值运算符配合,使得编译器在对象传递和赋值时能够根据对象是左值还是右值选择最合适的函数。对于右值对象(临时对象等),使用移动操作可以避免深拷贝,提高性能;对于左值对象,仍然使用拷贝构造函数和赋值运算符进行深拷贝,以确保数据安全。如果没有自定义移动构造函数和移动赋值运算符,编译器可能会使用缺省的拷贝构造函数和赋值运算符,导致不必要的性能开销。