MST

星途 面试题库

面试题:C++ 赋值运算符与拷贝构造函数在复杂对象中的性能考量

考虑一个包含多个复杂成员对象(如 `std::vector`、自定义的智能指针等)的 C++ 类 `ComplexClass`。在实现拷贝构造函数和赋值运算符重载时,如何优化性能以避免不必要的资源重复分配和释放?举例说明不同优化策略对性能的影响。
41.9万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. 浅拷贝与深拷贝

  • 浅拷贝:默认的拷贝构造函数和赋值运算符重载是浅拷贝,对于包含指针或动态分配资源的成员,只是简单地复制指针的值,这会导致多个对象共享同一块资源,在析构时可能出现重复释放或悬空指针问题。例如:
class SimpleClass {
public:
    int* data;
    SimpleClass(int value) {
        data = new int(value);
    }
    // 默认浅拷贝构造函数
    SimpleClass(const SimpleClass& other) {
        data = other.data;
    }
    // 默认浅拷贝赋值运算符重载
    SimpleClass& operator=(const SimpleClass& other) {
        if (this != &other) {
            data = other.data;
        }
        return *this;
    }
    ~SimpleClass() {
        delete data;
    }
};

这种方式对于 ComplexClass 包含 std::vector 或自定义智能指针时会有严重问题,因为这些容器或指针管理的资源不应简单共享。

  • 深拷贝:在拷贝构造函数和赋值运算符重载中,为每个动态分配的资源重新分配内存并复制内容,以确保每个对象拥有独立的资源。对于 ComplexClass 中的 std::vector
#include <vector>
class ComplexClass {
public:
    std::vector<int> vec;
    // 深拷贝构造函数
    ComplexClass(const ComplexClass& other) {
        vec = other.vec; // std::vector 会自动进行深拷贝
    }
    // 深拷贝赋值运算符重载
    ComplexClass& operator=(const ComplexClass& other) {
        if (this != &other) {
            vec = other.vec;
        }
        return *this;
    }
};

对于自定义智能指针,假设 MySmartPtr 是一个简单的自定义智能指针:

template <typename T>
class MySmartPtr {
public:
    T* ptr;
    MySmartPtr(T* p = nullptr) : ptr(p) {}
    ~MySmartPtr() {
        delete ptr;
    }
    // 深拷贝构造函数
    MySmartPtr(const MySmartPtr& other) {
        if (other.ptr) {
            ptr = new T(*other.ptr);
        } else {
            ptr = nullptr;
        }
    }
    // 深拷贝赋值运算符重载
    MySmartPtr& operator=(const MySmartPtr& other) {
        if (this != &other) {
            delete ptr;
            if (other.ptr) {
                ptr = new T(*other.ptr);
            } else {
                ptr = nullptr;
            }
        }
        return *this;
    }
};

class ComplexClassWithSmartPtr {
public:
    MySmartPtr<int> smartPtr;
    // 深拷贝构造函数
    ComplexClassWithSmartPtr(const ComplexClassWithSmartPtr& other) {
        smartPtr = other.smartPtr;
    }
    // 深拷贝赋值运算符重载
    ComplexClassWithSmartPtr& operator=(const ComplexClassWithSmartPtr& other) {
        if (this != &other) {
            smartPtr = other.smartPtr;
        }
        return *this;
    }
};

2. 优化策略

  • 移动语义:C++11 引入了移动语义,在对象所有权转移时避免不必要的资源复制。对于 ComplexClass 中的 std::vector,可以实现移动构造函数和移动赋值运算符:
class ComplexClass {
public:
    std::vector<int> vec;
    // 移动构造函数
    ComplexClass(ComplexClass&& other) noexcept {
        vec = std::move(other.vec);
    }
    // 移动赋值运算符
    ComplexClass& operator=(ComplexClass&& other) noexcept {
        if (this != &other) {
            vec = std::move(other.vec);
        }
        return *this;
    }
};

对于自定义智能指针 MySmartPtr

template <typename T>
class MySmartPtr {
public:
    T* ptr;
    MySmartPtr(T* p = nullptr) : ptr(p) {}
    ~MySmartPtr() {
        delete ptr;
    }
    // 移动构造函数
    MySmartPtr(MySmartPtr&& other) noexcept {
        ptr = other.ptr;
        other.ptr = nullptr;
    }
    // 移动赋值运算符
    MySmartPtr& operator=(MySmartPtr&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }
};

class ComplexClassWithSmartPtr {
public:
    MySmartPtr<int> smartPtr;
    // 移动构造函数
    ComplexClassWithSmartPtr(ComplexClassWithSmartPtr&& other) noexcept {
        smartPtr = std::move(other.smartPtr);
    }
    // 移动赋值运算符
    ComplexClassWithSmartPtr& operator=(ComplexClassWithSmartPtr&& other) noexcept {
        if (this != &other) {
            smartPtr = std::move(other.smartPtr);
        }
        return *this;
    }
};

移动语义在性能上比深拷贝有显著提升,因为它只是转移资源的所有权,而不是复制资源。例如,在将一个大的 std::vector 从一个对象移动到另一个对象时,无需重新分配内存和复制元素,大大减少了时间和空间开销。

  • 引用计数:对于某些场景,可以使用引用计数来优化资源管理。例如,std::shared_ptr 就是基于引用计数的智能指针。如果 ComplexClass 中的某个成员使用 std::shared_ptr,则多个 ComplexClass 对象可以共享同一个资源,只有当最后一个引用该资源的对象销毁时,资源才会被释放。
#include <memory>
class ComplexClassWithSharedPtr {
public:
    std::shared_ptr<int> sharedPtr;
    // 拷贝构造函数
    ComplexClassWithSharedPtr(const ComplexClassWithSharedPtr& other) {
        sharedPtr = other.sharedPtr;
    }
    // 赋值运算符重载
    ComplexClassWithSharedPtr& operator=(const ComplexClassWithSharedPtr& other) {
        if (this != &other) {
            sharedPtr = other.sharedPtr;
        }
        return *this;
    }
};

引用计数在资源共享频繁且资源创建和销毁开销较大的场景下能有效提升性能,但会增加一定的内存开销用于维护引用计数。

性能影响举例

假设 ComplexClass 中的 std::vector 包含大量元素,通过深拷贝构造函数创建新对象时,需要重新分配内存并复制所有元素,时间复杂度为 O(n),n 为元素个数。而使用移动构造函数时,只是转移资源所有权,时间复杂度接近 O(1)。对于引用计数,如果多个 ComplexClass 对象频繁共享资源,使用 std::shared_ptr 能减少资源的重复创建和销毁,提升性能,但引用计数的维护也会带来一定开销。例如,在一个频繁创建和销毁 ComplexClass 对象且对象间共享资源的场景中,使用引用计数的方式比每次深拷贝能显著提升性能。但如果对象间很少共享资源,引用计数的开销可能会抵消其优势。