C++ std::shared_ptr的aliasing constructor使用场景
std::shared_ptr 的 aliasing constructor 是一个强大但常被忽视的特性。它允许你创建一个 shared_ptr,该指针管理一个对象,但其生命周期不由该对象本身决定,而是由另一个已有的 shared_ptr 决定。这听起来可能有些绕,但它在特定场景下非常有用。
核心概念
std::shared_ptr 的 aliasing constructor 的签名如下:
template <class Y>
shared_ptr(shared_ptr<X> const& r, Y* p);
r: 一个已有的shared_ptr<X>,新创建的shared_ptr<Y>将共享其引用计数。p: 一个指向Y类型对象的指针。
新创建的 shared_ptr<Y> 的引用计数与 r 相关联,而不是与 p 相关联。这意味着,当 shared_ptr<Y> 被销毁时,只会减少 r 的引用计数,而不会影响 p 所指向对象的生命周期(除非 p 和 r 指向同一个对象)。
使用场景 1: 资源管理不与对象生命周期绑定
这是最经典的使用场景。假设你有一个类管理一个资源句柄,但该句柄指向的数据由另一个对象管理。
步骤 1: 定义一个管理文件句柄的类
#include <iostream>
#include <memory>
#include <string>
class FileData {
public:
FileData(const std::string& data) : data_(data) {
std::cout << "FileData constructed with: " << data_ << std::endl;
}
~FileData() {
std::cout << "FileData destroyed with: " << data_ << std::endl;
}
const std::string& getData() const { return data_; }
private:
std::string data_;
};
class FileHandle {
public:
FileHandle(const std::string& filename, std::shared_ptr<FileData> data)
: filename_(filename), data_(data) {}
void printData() const {
if (data_) {
std::cout << "File '" << filename_ << "' contains: " << data_->getData() << std::endl;
} else {
std::cout << "File '" << filename_ << "' data is invalid." << std::endl;
}
}
private:
std::string filename_;
std::shared_ptr<FileData> data_;
};
步骤 2: 使用 aliasing constructor
int main() {
// 创建一个 FileData 对象,由 shared_ptr 管理
auto fileData = std::make_shared<FileData>("Hello, World!");
// 创建一个 FileHandle,它持有 fileData 的 shared_ptr
auto fileHandle = std::make_shared<FileHandle>("example.txt", fileData);
// 现在,我们想创建一个 shared_ptr<FileData>,它指向 fileData 的数据,
// 但不管理 fileData 的生命周期。我们使用 aliasing constructor。
std::shared_ptr<FileData> dataAlias(fileHandle, fileData.get());
std::cout << "------------------------" << std::endl;
std::cout << "Original data: " << fileData->getData() << std::endl;
std::cout << "Aliased data: " << dataAlias->getData() << std::endl;
std::cout << "------------------------" << std::endl;
// fileHandle 和 dataAlias 共享同一个引用计数
std::cout << "fileHandle use count: " << fileHandle.use_count() << std::endl; // 输出 2
std::cout << "dataAlias use count: " << dataAlias.use_count() << std::endl; // 输出 2
// 当 dataAlias 被销毁时,fileHandle 的引用计数会减少,但 fileData 仍然存在
dataAlias.reset();
std::cout << "After dataAlias.reset(), fileHandle use count: " << fileHandle.use_count() << std::endl; // 输出 1
// fileHandle 仍然有效,可以访问 data
fileHandle->printData();
// 当 fileHandle 被销毁时,fileData 的引用计数会减少到 0,fileData 被销毁
return 0;
}
输出:
FileData constructed with: Hello, World!
------------------------
Original data: Hello, World!
Aliased data: Hello, World!
------------------------
fileHandle use count: 2
dataAlias use count: 2
After dataAlias.reset(), fileHandle use count: 1
File 'example.txt' contains: Hello, World!
FileData destroyed with: Hello, World!
在这个例子中,dataAlias 共享 fileHandle 的引用计数,而不是管理 fileData 的生命周期。当 dataAlias 被销毁时,fileHandle 的引用计数减少,但 fileData 仍然存在,因为 fileHandle 仍然持有它。
使用场景 2: 实现观察者模式
在观察者模式中,观察者通常持有对被观察者的引用。使用 aliasing constructor 可以实现一种“弱引用”式的强引用,即观察者可以访问被观察者,但当被观察者被销毁时,观察者不会被销毁,而是可以检测到被观察者已经不存在。
步骤 1: 定义 Subject 和 Observer
#include <iostream>
#include <memory>
#include <string>
class Subject {
public:
void attach(std::shared_ptr<Observer> observer) {
observers_.push_back(observer);
}
void notify(const std::string& message) {
for (auto& observer : observers_) {
if (observer) {
observer->update(message);
}
}
}
~Subject() {
std::cout << "Subject destroyed." << std::endl;
}
private:
std::vector<std::shared_ptr<Observer>> observers_;
};
class Observer {
public:
Observer(const std::string& name, std::shared_ptr<Subject> subject)
: name_(name), subject_(subject, this) {} // 使用 aliasing constructor
void update(const std::string& message) {
std::cout << "Observer '" << name_ << "' received: " << message << std::endl;
}
void checkSubject() const {
if (subject_) {
std::cout << "Observer '" << name_ << "': Subject is still alive." << std::endl;
} else {
std::cout << "Observer '" << name_ << "': Subject has been destroyed." << std::endl;
}
}
private:
std::string name_;
std::shared_ptr<Observer> subject_; // 注意:这里存储的是指向自己的 shared_ptr,使用 aliasing constructor
};
步骤 2: 使用 Observer
int main() {
auto subject = std::make_shared<Subject>();
auto observer1 = std::make_shared<Observer>("Observer1", subject);
auto observer2 = std::make_shared<Observer>("Observer2", subject);
subject->notify("Hello Observers!");
// 当 subject 被销毁时,observer1 和 observer2 的引用计数会减少
subject.reset();
observer1->checkSubject(); // 输出: Observer 'Observer1': Subject has been destroyed.
observer2->checkSubject(); // 输出: Observer 'Observer2': Subject has been destroyed.
return 0;
}
输出:
Observer 'Observer1' received: Hello Observers!
Observer 'Observer2' received: Hello Observers!
Subject destroyed.
Observer 'Observer1': Subject has been destroyed.
Observer 'Observer2': Subject has been destroyed.
在这个例子中,Observer 的 subject_ 成员是一个 shared_ptr<Observer>,但它使用 aliasing constructor 指向 subject。这意味着 observer1 和 observer2 共享 subject 的引用计数。当 subject 被销毁时,observer1 和 observer2 仍然存在,并且可以检测到 subject 已经不存在。
使用场景 3: 缓存或对象池
在缓存或对象池中,你可能需要管理一个缓存对象,但缓存对象内部的数据可能被替换或更新。Alias constructor 可以用来创建一个指向新数据的 shared_ptr,同时保持缓存对象的生命周期。
步骤 1: 定义一个缓存类
#include <iostream>
#include <memory>
#include <string>
class CacheData {
public:
CacheData(const std::string& data) : data_(data) {
std::cout << "CacheData constructed with: " << data_ << std::endl;
}
~CacheData() {
std::cout << "CacheData destroyed with: " << data_ << std::endl;
}
const std::string& getData() const { return data_; }
private:
std::string data_;
};
class Cache {
public:
Cache() : data_(std::make_shared<CacheData>("Initial data")) {}
void updateData(const std::string& newData) {
// 使用 aliasing constructor 创建一个新的 shared_ptr<CacheData>
// 它共享 cache 对象的引用计数,但不管理 cache 对象的生命周期
data_ = std::shared_ptr<CacheData>(shared_from_this(), new CacheData(newData));
}
void printData() const {
if (data_) {
std::cout << "Cache data: " << data_->getData() << std::endl;
}
}
private:
std::shared_ptr<CacheData> data_;
};
步骤 2: 使用 Cache
int main() {
auto cache = std::make_shared<Cache>();
cache->printData(); // 输出: Cache data: Initial data
cache->updateData("Updated data");
cache->printData(); // 输出: Cache data: Updated data
// 当 cache 被销毁时,CacheData 也被销毁
return 0;
}
输出:
CacheData constructed with: Initial data
Cache data: Initial data
CacheData constructed with: Updated data
Cache data: Updated data
CacheData destroyed with: Updated data
CacheData destroyed with: Initial data
在这个例子中,Cache 类管理一个 CacheData 对象。当调用 updateData 时,使用 aliasing constructor 创建一个新的 CacheData,并共享 Cache 对象的引用计数。这意味着 Cache 对象的生命周期不受 CacheData 对象的影响,CacheData 对象只会在 Cache 对象被销毁时被销毁。
使用场景 4: 实现多态接口
当你有一个基类指针指向一个派生类对象,但你需要一个指向派生类内部成员的 shared_ptr 时,aliasing constructor 可以派上用场。
步骤 1: 定义基类和派生类
#include <iostream>
#include <memory>
#include <string>
class Base {
public:
virtual ~Base() = default;
virtual void print() const = 0;
};
class Derived : public Base {
public:
Derived(const std::string& data) : data_(data) {
std::cout << "Derived constructed with: " << data_ << std::endl;
}
~Derived() override {
std::cout << "Derived destroyed with: " << data_ << std::endl;
}
void print() const override {
std::cout << "Derived data: " << data_ << std::endl;
}
const std::string& getData() const { return data_; }
private:
std::string data_;
};
步骤 2: 使用 aliasing constructor
int main() {
auto derived = std::make_shared<Derived>("Hello, Polymorphism!");
// 创建一个 shared_ptr<Base> 指向 derived
std::shared_ptr<Base> basePtr = derived;
// 现在,我们想创建一个 shared_ptr<std::string>,它指向 derived 的 data_ 成员
// 我们使用 aliasing constructor
std::shared_ptr<std::string> dataPtr(basePtr, &derived->getData());
std::cout << "------------------------" << std::endl;
std::cout << "Derived data: " << derived->getData() << std::endl;
std::cout << "Data pointer: " << *dataPtr << std::endl;
std::cout << "------------------------" << std::endl;
// basePtr 和 dataPtr 共享同一个引用计数
std::cout << "basePtr use count: " << basePtr.use_count() << std::endl; // 输出 2
std::cout << "dataPtr use count: " << dataPtr.use_count() << std::endl; // 输出 2
// 当 dataPtr 被销毁时,basePtr 的引用计数会减少,但 derived 仍然存在
dataPtr.reset();
std::cout << "After dataPtr.reset(), basePtr use count: " << basePtr.use_count() << std::endl; // 输出 1
// basePtr 仍然有效,可以调用 print
basePtr->print();
// 当 basePtr 被销毁时,derived 的引用计数会减少到 0,derived 被销毁
return 0;
}
输出:
Derived constructed with: Hello, Polymorphism!
------------------------
Derived data: Hello, Polymorphism!
Data pointer: Hello, Polymorphism!
------------------------
basePtr use count: 2
dataPtr use count: 2
After dataPtr.reset(), basePtr use count: 1
Derived data: Hello, Polymorphism!
Derived destroyed with: Hello, Polymorphism!
在这个例子中,dataPtr 共享 basePtr 的引用计数,而不是管理 derived 的生命周期。当 dataPtr 被销毁时,basePtr 的引用计数减少,但 derived 仍然存在,因为 basePtr 仍然持有它。
注意事项
- 生命周期管理:
Y* p的生命周期必须至少和shared_ptr<X>一样长。否则,当shared_ptr<X>被销毁时,Y* p可能已经失效,导致悬垂指针。 - 引用计数:
shared_ptr<Y>的引用计数与shared_ptr<X>相关联,而不是与Y* p相关联。这意味着,当shared_ptr<Y>被销毁时,只会减少shared_ptr<X>的引用计数,而不会影响Y* p所指向对象的生命周期(除非p和r.get()指向同一个对象)。 - 性能: Alias constructor 的性能与普通
shared_ptr构造函数相当,因为它只是共享一个现有的引用计数块,而不需要分配新的内存。
总结
std::shared_ptr 的 aliasing constructor 是一个强大的工具,它允许你创建一个 shared_ptr,该指针管理一个对象,但其生命周期不由该对象本身决定,而是由另一个已有的 shared_ptr 决定。它在资源管理、观察者模式、缓存和对象池、多态接口等场景中非常有用。理解其工作原理和注意事项,可以帮助你编写更灵活、更安全的 C++ 代码。

暂无评论,快来抢沙发吧!