[C++面试] 智能指针面试点(重点)续2

[C++面试] RAII资源获取即初始化(重点)-优快云博客

[C++面试] 智能指针面试点(重点)-优快云博客

[C++面试] 智能指针面试点(重点)续1-优快云博客

一、入门

1、智能指针支持加减吗?为什么?

智能指针(如std::shared_ptrstd::unique_ptr不支持直接的加减操作。这是因为智能指针的设计目标是安全地管理资源所有权,而非像原始指针那样进行内存地址的算术运算。

  • 原始指针:允许通过加减操作(如p + n)直接访问内存地址,适用于数组遍历等场景。
  • 智能指针:专注于资源管理(如自动释放内存),不鼓励直接操作内存地址。加减操作可能破坏其封装性,导致悬空指针或未定义行为。
  • 加减操作可能导致智能指针指向非法内存(如已释放的对象)

二、进阶

1、​unique_ptr与五法则的关系

五法则(Rule of Five)是指:当一个类需要自定义析构函数拷贝构造函数拷贝赋值运算符时,通常也需要自定义移动构造函数移动赋值运算符

五法则要素unique_ptr的实现方式
析构函数默认释放资源,无需自定义。
拷贝构造函数显式删除(= delete),禁止拷贝。
拷贝赋值运算符显式删除(= delete),禁止拷贝。
移动构造函数编译器自动生成,正确转移资源所有权。
移动赋值运算符编译器自动生成,正确转移资源所有权。

std::unique_ptr 是一种独占式智能指针,同一时间只能有一个 std::unique_ptr 指向某个对象。为了保证这种独占性,std::unique_ptr 对拷贝构造函数和赋值运算符进行了限制,具体做法是将它们删除(在 C++ 中,使用 = delete 语法)

template <typename T>
class SimpleUniquePtr {
public:
    // 构造函数
    explicit SimpleUniquePtr(T* ptr = nullptr) : data(ptr) {}

    // 析构函数
    ~SimpleUniquePtr() {
        delete data;
    }

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

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

    // 删除拷贝构造函数
    SimpleUniquePtr(const SimpleUniquePtr& other) = delete;

    // 删除赋值运算符
    SimpleUniquePtr& operator=(const SimpleUniquePtr& other) = delete;

private:
    T* data;
};
SimpleUniquePtr<int> ptr1(new int(42));
// SimpleUniquePtr<int> ptr2 = ptr1;  // 错误,拷贝构造函数已删除
// SimpleUniquePtr<int> ptr2(ptr1);  // 错误,拷贝构造函数已删除

// SimpleUniquePtr<int> ptr3;
// ptr3 = ptr1;  // 错误,赋值运算符已删除

2、智能指针与内存位置的关系 

无论智能指针本身位于何处,其管理的对象必须是动态分配的资源​(堆内存或需手动释放的资源,如文件句柄)

智能指针理论上可管理栈内存(需自定义删除器阻止释放操作),但违反设计初衷

int x = 10;
auto ptr = std::shared_ptr<int>(&x, [](int*){});  // 自定义空删除器

对象类型存储位置是否需要智能指针示例
智能指针管理的对象堆内存必须std::make_shared<int>(42)
智能指针自身栈/堆/静态区通常栈std::unique_ptr<int> uPtr(...)
普通栈对象栈内存不需要int x = 10;

智能指针的主要功能是自动管理动态分配的内存​(即堆内存),解决手动new/delete可能引发的内存泄漏问题。栈内存由编译器自动管理生命周期,无需智能指针干预

  • 堆内存分配:通过newstd::make_shared/std::make_unique动态分配堆内存,智能指针接管其所有权
  • 控制块存储:shared_ptr的控制块(含引用计数)通常与对象一起分配在堆上,以支持多指针共享
auto ptr = std::make_shared<int>(42);  // 对象和控制块均在堆上

智能指针变量ptr本身在栈上,但其管理的int对象和引用计数控制块均在堆内存中 

// 最常见的用法,利用栈对象自动析构特性释放资源
std::unique_ptr<int> uPtr(new int(10));  // uPtr在栈上

// 智能指针本身可通过new动态分配(但需手动管理其自身生命周期,不推荐)
auto* p = new std::unique_ptr<int>(new int(20)); 

 3、使用 unique_ptr 类实例化一个 Carp 对象,而 Carp 类继承了 Fish 类。将该对象作为 Fish 指针传递时是否会出现切除问题?

会出现切除问题。unique_ptr 是一种独占式智能指针。

当使用unique_ptr 实例化一个Carp对象,并将其作为Fish指针传递时,由于unique_ptr 不支持隐式转换(不像shared_ptr 有控制块可以处理类型转换等复杂情况 ),如果直接进行指针转换传递,会导致Carp对象中特有的成员被 “切除”(即丢失派生类特有的部分 ),因为Fish指针只能指向Fish类对象,而无法完整表示Carp对象。

正确的做法可以是使用std::shared_ptr 等支持多态的智能指针,或者在设计上避免这种直接的指针传递方式。

三、高阶

1、 写时复制机制(Copy on Write,COW)

主要用于读多写少的场景,通过延迟深拷贝操作来提升性能。

初始共享:多个智能指针共享同一份数据,仅持有对原数据的引用(或浅拷贝),避免立即执行深拷贝的开销

触发复制条件:当某个指针尝试对数据进行非只读操作​(如修改数据)时,才会触发深拷贝,创建数据副本供修改,其他指针仍共享原数据

通过引用计数跟踪共享数据的指针数量。若引用计数大于1且需修改数据,则执行深拷贝;若引用计数为1,则直接修改原数据。shared_ptr结合unique()方法判断是否需要复制。

关键设计:*->运算符分别提供const非const版本。非const操作触发深拷贝:

if (!data.unique()) {  // 判断是否唯一所有者
    data = std::make_shared<T>(*data);  // 深拷贝
}
// 修改data指向的副本
#include <iostream>
#include <memory>
#include <string>

class CowString {
private:
    // 使用 std::shared_ptr 管理字符串数据
    std::shared_ptr<std::string> data;

    // 复制数据并返回新的 shared_ptr
    std::shared_ptr<std::string> copyOnWrite() {
        if (data.use_count() > 1) {
            // 如果引用计数大于 1,复制一份数据
            return std::make_shared<std::string>(*data);
        }
        return data;
    }

public:
    // 构造函数
    CowString(const std::string& str = "") : data(std::make_shared<std::string>(str)) {}

    // 拷贝构造函数
    CowString(const CowString& other) : data(other.data) {}

    // 赋值运算符
    CowString& operator=(const CowString& other) {
        if (this != &other) {
            data = other.data;
        }
        return *this;
    }

    // 获取字符串的常量引用
    const std::string& get() const {
        return *data;
    }

    // 修改字符串的某个字符
    void setChar(size_t index, char c) {
        data = copyOnWrite();
        (*data)[index] = c;
    }
};

int main() {
    CowString str1("Hello");
    CowString str2 = str1;

    std::cout << "str1: " << str1.get() << std::endl;
    std::cout << "str2: " << str2.get() << std::endl;

    // 修改 str2 的第一个字符
    str2.setChar(0, 'J');

    std::cout << "str1: " << str1.get() << std::endl;
    std::cout << "str2: " << str2.get() << std::endl;

    return 0;
}

四、其他

1、现在C++里不要用new、delete

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值