永不建立auto_ptr的容器

本文探讨了C++中auto_ptr的特殊行为,特别是拷贝构造和赋值操作导致的所有权转移,并通过排序vector的示例展示了潜在的问题。

请注意拷贝auto_ptr的独特性!


auto_ptr<Widget> pw1(new Widget);        // pw1指向一个Widget 
auto_ptr<Widget> pw2(pw1);                    // pw2指向pw1的Widget; 
                                        // pw1被设为NULL。(Widget的
                                        // 所有权从pw1转移到pw2。)
pw1 = pw2;                               // pw1现在再次指向Widget;
                                        // pw2被设为NULL



例如下面排序的例子(C++编译器禁止auto_ptr容器,所以这个例子不能编译通过)

bool widgetAPCompare(const auto_ptr<Widget>& lhs, const auto_ptr<Widget>& rhs) {
        return *lhs < *rhs;             // 对于这个例子,假设Widget
}                                       // 存在operator<


vector<auto_ptr<Widget> > widgets;         // 建立一个vector,然后
                                        // 用Widget的auto_ptr填充它;
                                        // 记住这将不能编译!
sort(widgets.begin(), widgets.end(), widgetAPCompare);  // 排序这个vector


这里的所有东西看起来都很合理,而且从概念上看所有东西也都很合理,但结果却完全不合理。例如,在排序过程中widgets中的一个或多个auto_ptr可能已经被设为NULL。排序这个vector的行为可能已经改变了它的内容!值得去了解这是怎么发生的。

1. 简答题(每题 5 分,共 15 分) 1. 简述 `std::unique_ptr` 和 `std::shared_ptr` 的主要区别,并指出它们各自适用的典型场景。 答:`std::unique_ptr` 和 `std::shared_ptr`的主要区别是资源权利是否共享。 `std::unique_ptr`离开作用域后自动释放资源,通过其析构函数自动释放资源。 `std::shared_ptr`使用引用计数机制,一个指向资源的`std::shared_ptr`的智能指针离开作用域时,引用计数减1,当最后一个指向资源的`std::shared_ptr`智能指针离开作用域,即引用计数为0时释放资源。 `std::unique_ptr`适用于需要独占资源且出作用域自动释放资源的场景。`std::shared_ptr`适用于共享资源权利的场景,对于多线程共享资源,此种智能指针也是支持的,因为其引用计数使用原子整数来实现。 2. 什么是循环引用?如何避免 `std::shared_ptr` 导致的循环引用? 答:两个或多个`std::shared_ptr`相互引用,形成一个环状态结构,这种情况称为循环引用,最终结果会导致内存泄漏。举例说明 ```cpp struct A{ std::shared_ptr<B> b_; }; struct B{ std::shared_ptr<A> a_; }; void test() { std::shared_ptr<A> pa=std::make_shared<A>(); std::shared_ptr<B> pb=std::make_shared<B>(); pa->b_ = pb; pb->a_ = pa; } ``` 分析test的调用过程,创建两个`std::shared_ptr`智能指针,各自引用计数为1。当给各个成员赋值完成,两个智能指针的引用计数都为2。当出作用域时,pa与pb引用计数都减为1,都没有使得引用计数减为零来释放内存。最终结果形成一个孤立的环。 使用`std::weak_ptr`类型的智能指针,它的创建与销毁不会引起引用计数的增减,且在不打破循环引用的情况,也保证了内存不会泄漏。 3. 解释 `std::enable_shared_from_this` 的作用,并给出一个使用场景。 答:未使用过 2. 判断题(每题 2 分,共 10 分) 判断以下代码是否存在内存泄漏或悬空指针问题,并简要说明理由。 ```cpp std::shared_ptr<int> sp1(new int(10)); std::shared_ptr<int> sp2 = sp1; sp1.reset(); std::cout << *sp2 << std::endl; ``` 答:不会内存泄漏或悬空指针问题,sp1.reset()保证引用计数减1。 ```cpp std::unique_ptr<int> up(new int(5)); auto raw = up.get(); up.reset(); std::cout << *raw << std::endl; ``` 答:有悬空指针问题,up.reset()导致其指向的资源释放,结果就是raw指针悬空。 ```cpp std::shared_ptr<int> sp(new int(20)); std::weak_ptr<int> wp = sp; sp.reset(); if (auto locked = wp.lock()) { std::cout << *locked << std::endl; } ``` 答:不会由内存泄漏或悬空指针问题,在sp.reset()后,sp指向了空指针。调用wp.lock(),由于其由sp而来,sp己指向空指针,因此将返回指向空的`std::shared_ptr`。 3. 选择题(单选,每题 1 分,共 5 分) 1. 使用 `std::make_shared` 相较于 `std::shared_ptr<T>(new T)` 的优势是: C - A. 性能更好,内存分配更高效 - B. 更安全,避免内存泄漏 - C. 支持数组类型 - D. A 和 B 都正确 2. 下列关于 `std::weak_ptr` 的说法正确的是: C - A. 可以直接解引用访问对象 - B. 可以延长对象生命周期 - C. 用于打破 `shared_ptr` 的循环引用 - D. 是线程不安全的 --- 💻 二、编程题(共 70 分) 题目 1:线程安全的对象池(40 分) 要求: 实现一个线程安全的对象池类 `ObjectPool<T>`,满足以下条件: - 使用 `std::unique_ptr<T>` 管理对象生命周期; - 支持自定义 deleter; - 提供 `Acquire()` 和 `Release()` 方法; - 使用互斥锁保证线程安全; - 对象池初始大小为 10,支持动态扩展; - 禁止拷贝构造和赋值操作; - 提供方法 `Size()` 返回当前池中可用对象数量。 示例接口: ```cpp template <typename T> class ObjectPool { public: using Deleter = std::function<void(T*)>; ObjectPool(std::function<std::unique_ptr<T>()> creator, Deleter deleter); std::unique_ptr<T, Deleter> Acquire(); void Release(std::unique_ptr<T, Deleter> obj); size_t Size() const; }; ``` 评分标准: 项目 分值 正确实现对象池逻辑 15 分 使用 `unique_ptr` + 自定义 deleter 10 分 线程安全实现 10 分 禁止拷贝/赋值 2 分 提供 Size() 接口 3 分 ```cpp template <typename T> class ObjectPool { public: using Deleter = std::function<void(T*)>; ObjectPool(std::function<std::unique_ptr<T>()> creator, Deleter deleter) : creator_(creator), deleter_(deleter) { for (int i = 0; i < 10; i++) { objs_.push_back(std::unique_ptr<T, Deleter>(creator_().release())); } } ObjectPool(const ObjectPool&) = delete; ObjectPool& operator=(const ObjectPool&) = delete; std::unique_ptr<T, Deleter> Acquire() { std::unique_lock<std::mutex> lock(lock_); if (objs_.empty()) { objs_.push_back(std::unique_ptr<T, Deleter>(creator_().release())); } auto p = objs_[objs_.size() - 1].release(); objs_.pop_back(); return std::unique_ptr<T, Deleter>(p); } void Release(std::unique_ptr<T, Deleter> obj) { std::unique_lock<std::mutex> lock(lock_); objs_.push_back(obj.release()); } size_t Size() const { std::unique_lock<std::mutex> lock(lock_); return objs_.size(); } private: std::function<std::unique_ptr<T>()> creator_; Deleter deleter_; std::mutex lock_; std::vector<std::unique_ptr<T, Deleter>> objs_; }; ``` --- 题目 2:Qt 内存管理应用题(30 分) 背景: 你正在开发一个 Qt 应用程序,需要管理多个动态创建的 `QWidget` 子类对象(如窗口、按钮等)。这些对象的生命周期由父子关系部分管理,但某些对象需要在特定条件下手动释放。 要求: 1. 设计一个资源管理器类 `WidgetManager`,支持以下功能: - 使用 `QObject` 父子关系管理部分控件; - 使用智能指针(如 `std::unique_ptr` 或 `QSharedPointer`)管理非父子关系的控件; - 提供方法 `CreateWidget()` 创建控件; - 提供方法 `ReleaseWidget()` 手动释放控件; - 提供方法 `PrintMemoryStatus()` 打印当前存活控件数量; 2. 使用 Qt 的内存泄漏检测工具(如 `QML_DEBUG` 或 `valgrind`)验证无内存泄漏; 3. 简要说明你在哪些场景下使用智能指针,哪些场景使用父子关系。 评分标准: 项目 分值 正确设计 WidgetManager 类 10 分 正确结合 QObject 父子关系与智能指针 10 分 提供内存状态打印功能 5 分 使用检测工具验证无泄漏 5 分 --- ```cpp #include <QObject> #include <QWidget> #include <QSet> #include <QMap> #include <QDebug> class WidgetManager : public QObject { Q_OBJECT public: WidgetManager(QObject* parent) : QObject(parent) { } ~WidgetManager() { } WidgetManager(const WidgetManager&) = delete; WidgetManager& operator=(const WidgetManager&) = delete; template<class T> QWidget* CreateWidget(QWidget* parent) { QWidget* widget = new T(parent); if (parent) { haveParentWidgets_.insert(widget); connect(widget, &QObject::destroyed, this, [this, widget]() { haveParentWidgets_.erase(haveParentWidgets_.find(widget)); }); } else { noHaveParentWidgets_[widget] = QSharedPointer<QWidget>(widget); } return widget; } void ReleaseWidget(QWidget* widget) { if (widget->parent()) { auto it = haveParentWidgets_.find(widget); if (it != haveParentWidgets_.end()) { widget->deleteLater(); haveParentWidgets_.erase(it); } } else { auto it = noHaveParentWidgets_.find(widget); if (it != noHaveParentWidgets_.end()) { noHaveParentWidgets_.erase(it); } } } void PrintMemoryStatus() const { qDebug() << "Number of parent-manged widgets:" << haveParentWidgets_.size(); qDebug() << "Number of noparent-managed widets:" << noHaveParentWidgets_.size(); } private: QSet<QWidget *> haveParentWidgets_; QMap<QWidget *, QSharedPointer<QWidget>> noHaveParentWidgets_; }; ``` 智能指针使用在需要此管理器统一管理的控件对象的情况,父子关系用于基于QT自带的机制来管理控件对象的情况。
最新发布
11-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值