MST

星途 面试题库

面试题:C++中复杂场景下拷贝构造函数的调用剖析

考虑一个C++类`ComplexClass`,它包含一个动态分配的数组成员变量。当从一个`ComplexClass`对象创建另一个对象,以及该对象作为函数返回值时,拷贝构造函数的调用情况是怎样的?详细分析其过程,并说明如何避免不必要的拷贝构造函数调用(如使用移动语义),请写出完整代码示例并加以解释。
12.2万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

拷贝构造函数调用情况分析

  1. 从一个ComplexClass对象创建另一个对象
    • 当使用一个已存在的ComplexClass对象来初始化另一个新的ComplexClass对象时,拷贝构造函数会被调用。例如:
    ComplexClass obj1;
    ComplexClass obj2(obj1);
    
    • 在这种情况下,obj2会通过拷贝obj1的数据成员来进行初始化,包括动态分配的数组内容。拷贝构造函数需要为新对象分配新的内存,并将原对象数组中的内容逐元素复制到新对象的数组中。
  2. 对象作为函数返回值
    • 当函数返回一个ComplexClass对象时,通常会调用拷贝构造函数来创建一个临时对象,将函数内部的局部对象的数据拷贝到这个临时对象中,然后返回该临时对象。例如:
    ComplexClass createComplexClass() {
        ComplexClass temp;
        return temp;
    }
    
    • return temp;这一行,会调用拷贝构造函数创建一个临时对象,将temp的数据拷贝到这个临时对象中,然后返回该临时对象。

避免不必要的拷贝构造函数调用(使用移动语义)

  1. 移动构造函数
    • 移动语义通过移动构造函数和移动赋值运算符来实现。移动构造函数允许我们将一个对象的资源(如动态分配的数组)“移动”到另一个对象,而不是进行深拷贝。这样可以避免不必要的内存分配和数据复制,提高性能。
    • 移动构造函数的参数是一个右值引用,通常使用&&表示。
  2. 完整代码示例
#include <iostream>
#include <cstring>

class ComplexClass {
private:
    int* data;
    size_t size;

public:
    // 构造函数
    ComplexClass(size_t s = 0) : size(s) {
        if (size > 0) {
            data = new int[size];
            std::memset(data, 0, size * sizeof(int));
        } else {
            data = nullptr;
        }
    }

    // 拷贝构造函数
    ComplexClass(const ComplexClass& other) : size(other.size) {
        if (size > 0) {
            data = new int[size];
            std::memcpy(data, other.data, size * sizeof(int));
        } else {
            data = nullptr;
        }
    }

    // 移动构造函数
    ComplexClass(ComplexClass&& other) noexcept : size(other.size), data(other.data) {
        other.size = 0;
        other.data = nullptr;
    }

    // 析构函数
    ~ComplexClass() {
        delete[] data;
    }

    // 拷贝赋值运算符
    ComplexClass& operator=(const ComplexClass& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            if (size > 0) {
                data = new int[size];
                std::memcpy(data, other.data, size * sizeof(int));
            } else {
                data = nullptr;
            }
        }
        return *this;
    }

    // 移动赋值运算符
    ComplexClass& operator=(ComplexClass&& other) noexcept {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = other.data;
            other.size = 0;
            other.data = nullptr;
        }
        return *this;
    }
};

ComplexClass createComplexClass() {
    ComplexClass temp(10);
    return temp;
}

int main() {
    ComplexClass obj1(5);
    ComplexClass obj2(obj1); // 调用拷贝构造函数
    ComplexClass obj3 = createComplexClass(); // 调用移动构造函数(如果支持RVO或NRVO,可能不调用任何构造函数)

    return 0;
}
  1. 代码解释
    • 构造函数:分配动态数组内存并初始化。
    • 拷贝构造函数:为新对象分配内存,并将原对象数组内容复制过来。
    • 移动构造函数:直接接管源对象的动态数组指针,并将源对象的指针设为nullptr,大小设为0。这样避免了内存分配和数据复制。
    • 析构函数:释放动态分配的数组内存。
    • 拷贝赋值运算符:释放原对象内存,分配新内存并复制数据。
    • 移动赋值运算符:释放原对象内存,接管源对象的数组指针并将源对象指针设为nullptr,大小设为0。
    • createComplexClass函数中返回temp对象时,由于支持移动语义,如果编译器支持RVO(返回值优化)或NRVO(命名返回值优化),可能不会调用任何构造函数。否则,会调用移动构造函数将temp的资源移动到返回的临时对象中。