c++ - smart - 使用observer_ptr




smart pointer介绍 (4)

它仅用于源自我文档吗?

是。

在库基础技术规范V2中构造std::observer_ptr究竟是什么意思?

在我看来,它所做的只是包裹一个裸T* ,如果它没有增加动态内存安全性,这似乎是一个多余的步骤。

在我的所有代码中,我使用std::unique_ptr ,我需要明确拥有对象和std::shared_ptr ,我可以在其中共享对象的所有权。

这非常有效并且可以防止意外解除引用已经被破坏的对象。

std::observer_ptr不能保证所观察对象的生命周期。

如果它是从std::unique_ptrstd::shared_ptr构造的,我会看到在这样的结构中使用,但任何只使用T*代码可能只是继续这样做,如果他们计划移动任何东西它将是std::shared_ptr和/或std::unique_ptr (取决于使用)。

给出一个简单的示例函数:

template<typename T>
auto func(std::observer_ptr<T> ptr){}

如果停止智能指针在观察它们时销毁它们存储的对象将会有用。

但如果我想观察std::shared_ptrstd::unique_ptr我必须写:

auto main() -> int{
    auto uptr = std::make_unique<int>(5);
    auto sptr = std::make_shared<int>(6);
    func(uptr.get());
    func(sptr.get());
}

这使得它不比以下更安全:

template<typename T>
auto func(T *ptr){}

那么,这个新结构的用途是什么?

它只是用于自我记录的来源吗?


当您需要共享访问但不需要共享所有权时

问题是原始指针仍然非常有用,并且具有完全可敬的用例场景。

原始指针智能指针管理时,它的清理是有保证的,因此,在智能指针的生命周期内,通过智能指针管理的原始指针访问实际数据是有意义的。

因此,当我们创建通常采用原始指针的函数时,一种承诺函数不会删除该指针的好方法是使用强类型类,如std::observer_ptr

将托管的原始指针作为参数传递给std::observer_ptr函数参数时,我们知道该函数不会delete它。

这是一个函数说“给我你的指针,我不会干涉它的分配,我会用它来观察”的方式。

顺便说一下,我并不热衷于名字std::observer_ptr因为这意味着你可以看,但不能触摸。 但事实并非如此。 我会更喜欢access_ptr

附加说明:

这是与std::shared_ptr不同的用例。 std::shared_ptr是关于共享所有权的只有当你无法确定哪个拥有对象首先超出范围时才应该使用它。

另一方面, std::observer_ptr适用于您想要共享访问但不是所有权的时间

仅使用std::shared_ptr来共享访问权限并不合适,因为这可能效率很低。

因此,无论您是使用std::unique_ptr还是std::shared_ptr管理目标指针,仍然存在原始指针的用例,因此std::observer_ptr是合理的。


使用std::observer_ptr不是原始指针的一个好结果是它提供了一个更好的替代方案,可以替代从C继承的令人困惑且容易出错的多指针实例化语法。

std::observer_ptr<int> a, b, c;

是一个改进

int *a, *b, *c;

从C ++的角度来看这有点奇怪,很容易被误输

int* a, b, c;

是的, std::observer_ptr的重点主要是“自我记录”,这本身就是一个有效的结束。 但应该指出,可以说它并没有做得很好,因为“观察者”指针究竟是什么并不明显。 首先,正如Galik所指出的,对某些人而言,这个名称似乎意味着承诺不修改目标,这不是意图,所以像access_ptr这样的名字会更好。 第二,没有任何限定词,这个名字就意味着赞同它的“非功能性”行为。 例如,有人可能认为std::weak_ptr是一种“观察者”指针。 但是std::weak_ptr通过提供允许尝试访问(解除分配的)对象以安全地失败的机制来适应指针超过目标对象的情况。 std::observer_ptr的实现不适合这种情况。 所以也许raw_access_ptr会是一个更好的名字,因为它会更好地表明它的功能缺点。

那么,正如你有理由问的那样,这个功能上受到挑战的“非拥有”指针有什么意义呢? 主要原因可能是表现。 许多C ++程序员认为std::share_ptr的开销太高,因此只需要在需要“观察者”指针时使用原始指针。 建议的std::observer_ptr试图以可接受的性能成本提供代码清晰度的小幅改进。 具体而言,零性能成本。

不幸的是,似乎有一种普遍存在,但在我看来,对于将原始指针用作“观察者”指针是多么安全是不切实际的乐观主义。 特别是,虽然很容易声明目标对象必须比std::observer_ptr更长的要求,但绝对肯定它是否满足并不总是容易的。 考虑这个例子:

struct employee_t {
    employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
    std::string m_first_name;
    std::string m_last_name;
};

void replace_last_employee_with(const std::observer_ptr<employee_t> p_new_employee, std::list<employee_t>& employee_list) {
    if (1 <= employee_list.size()) {
        employee_list.pop_back();
    }
    employee_list.push_back(*p_new_employee);
}

void main(int argc, char* argv[]) {
    std::list<employee_t> current_employee_list;
    current_employee_list.push_back(employee_t("Julie", "Jones"));
    current_employee_list.push_back(employee_t("John", "Smith"));

    std::observer_ptr<employee_t> p_person_who_convinces_boss_to_rehire_him(&(current_employee_list.back()));
    replace_last_employee_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}

对于replace_last_employee_with()函数的作者,可能永远不会发生对新雇员的引用也可能是对要替换的现有雇员的引用,在这种情况下,该函数可能无意中导致其std::observer_ptr<employee_t>的目标std::observer_ptr<employee_t>参数在完成使用之前要解除分配。

这是一个人为的例子,但在更复杂的情况下,这种事情很容易发生。 当然,在绝大多数情况下使用原始指针是完全安全的。 问题是,在少数情况下,很容易认为它确实是安全的。

如果用std::shared_ptrstd::weak_ptr替换std::observer_ptr<employee_t>参数是出于任何不可接受的原因,那么现在有另一个安全选项 - 这是答案的无耻插件部分 - “ 注册指针 ”。 “注册指针”是智能指针,其行为与原始指针一样,除了它们在目标对象被销毁时(自动)设置为null_ptr ,并且默认情况下,如果您尝试访问已经存在的对象,则会抛出异常删除。 它们通常比std :: shared_ptrs faster ,但是如果你的性能要求非常严格,那么注册指针可以被“禁用”(自动替换为它们的原始指针对应物)和编译时指令,允许它们被使用(和招致)开销)仅在调试/测试/测试模式下。

因此,如果有一个基于原始指针的“观察者”指针,那么可以说应该有一个基于已注册的指针,也许正如OP建议的那样,一个基于std :: shared_ptr。







c++17