文章目录

C++ std::shared_ptr的aliasing constructor使用场景

发布于 2026-05-09 16:26:27 · 浏览 14 次 · 评论 0 条

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 所指向对象的生命周期(除非 pr 指向同一个对象)。


使用场景 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.

在这个例子中,Observersubject_ 成员是一个 shared_ptr<Observer>,但它使用 aliasing constructor 指向 subject。这意味着 observer1observer2 共享 subject 的引用计数。当 subject 被销毁时,observer1observer2 仍然存在,并且可以检测到 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 仍然持有它。


注意事项

  1. 生命周期管理: Y* p 的生命周期必须至少和 shared_ptr<X> 一样长。否则,当 shared_ptr<X> 被销毁时,Y* p 可能已经失效,导致悬垂指针。
  2. 引用计数: shared_ptr<Y> 的引用计数与 shared_ptr<X> 相关联,而不是与 Y* p 相关联。这意味着,当 shared_ptr<Y> 被销毁时,只会减少 shared_ptr<X> 的引用计数,而不会影响 Y* p 所指向对象的生命周期(除非 pr.get() 指向同一个对象)。
  3. 性能: Alias constructor 的性能与普通 shared_ptr 构造函数相当,因为它只是共享一个现有的引用计数块,而不需要分配新的内存。

总结

std::shared_ptr 的 aliasing constructor 是一个强大的工具,它允许你创建一个 shared_ptr,该指针管理一个对象,但其生命周期不由该对象本身决定,而是由另一个已有的 shared_ptr 决定。它在资源管理、观察者模式、缓存和对象池、多态接口等场景中非常有用。理解其工作原理和注意事项,可以帮助你编写更灵活、更安全的 C++ 代码。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文