【问题标题】:Scoped std::unique_ptr cast作用域 std::unique_ptr 演员表
【发布时间】:2015-10-08 23:09:39
【问题描述】:

我目前正在编写一些使用智能指针的代码,其中有必要在多个点将这些指针转换为它们的基本类型并将它们作为 const 参数传递给函数。目前我正在使用 shared_ptr 和标准指针转换函数来实现这一点,但这似乎效率低下(因为每次转换至少需要一个 CAS)并且还具有误导性(因为我们没有对共享关系进行建模,所以父级是唯一的所有者对象)。

因此我想出了以下方法,但想检查它是否确实安全,或者是否有一些边缘情况会破坏它?

template <typename ToType, typename FromType>
class FTScopedCastWrapper {
public:
    explicit FTScopedCastWrapper(std::unique_ptr<FromType>& p) : from_ptr_(&p) {
        auto d = static_cast<ToType *>(p.release());
        to_ptr_ = std::unique_ptr<ToType>(d);
    }

    ~FTScopedCastWrapper() {
        auto d = static_cast<FromType *>(to_ptr_.release());
        (*from_ptr_) = std::unique_ptr<FromType>(d);
    }

    const std::unique_ptr<ToType>& operator()() {
        return to_ptr_;
    }


    // Prevent allocation on the heap
    void* operator new(size_t) = delete;
    void* operator new(size_t, void*) = delete;
    void* operator new[](size_t) = delete;
    void* operator new[](size_t, void*) = delete;

private:
    std::unique_ptr<FromType>* from_ptr_;
    std::unique_ptr<ToType> to_ptr_;
};

template <typename ToType, typename FromType>
FTScopedCastWrapper<ToType, FromType> FTScopedCast(std::unique_ptr<FromType>& p) {
    return FTScopedCastWrapper<ToType, FromType>(p);
}

那么预期的用途是

void testMethod(const std::unique_ptr<Base>& ptr) {
    // Do Stuff
}

auto ptr = std::make_unique<Derived>();
testMethod(FTScopedCast<Base>(ptr)());

删除器不会被传递,因为这样做会阻止向上转换。这样做也没有意义,因为无论如何都不会在创建的智能指针上调用删除器。

堆上的分配被阻止,因为它可能允许包装器比它包装的指针寿命更长,复制被 std::unique_ptr 成员阻止,标准销毁顺序将确保原始指针返回到它之前的原始智能指针被销毁,即使它被声明在与包装器相同的范围内。

我知道这不是线程安全的,但我认为在线程之间共享 unique_ptr 会违反其单一所有者的合同。

【问题讨论】:

  • 考虑翻转逻辑并让对象调用函数。他们知道自己的类型,所以不再有演员表。
  • 如果testMethod 不打算影响所有权,那么为什么还要费心让它引用一个unique_ptr 呢?似乎使用Base*Base&amp; 会简化这种情况,并且无需处理任何类型的智能指针。
  • @TheUndeadFish 你说得对,事实上界面过去看起来是这样的,我只是决定是时候拥抱 C++11 并且可能有点过分热情了。

标签: c++ pointers c++11 smart-pointers


【解决方案1】:

如果我理解正确,其目的是在函数调用期间“窃取”std::unique_ptr 的内容,然后在函数调用完成后将其归还给它的原始所有者。

但这似乎是不必要的复杂。首先,正如 cmets 中的 @TheUndeadFish 所指出的,您可以将原始 Base* 作为函数参数并使用 std::unique_ptr::get() 调用它。只要被调用的函数没有做一些愚蠢的事情,比如在传入的指针上调用 delete 或将其隐藏在静态变量中以供以后使用,那么它就可以正常工作。

或者,如果您发现原始指针完全令人反感,您可以使用非拥有指针包装器,如下所示(未经测试,但您明白了):

template <typename T>
class unowned_ptr {
public:
    unowned_ptr() = default;
    unowned_ptr(const unowned_ptr&) = default;
    unowned_ptr& operator=(const unowned_ptr&) = default;

    template <typename U>
    unowned_ptr(const U* other) : ptr(other) {}

    template <typename U>
    unowned_ptr(const std::unique_ptr<U>& other) : ptr(other.get()) {}

    T* operator->() { return ptr; }
    const T* operator->() const { return ptr; }

private:
    T* ptr = nullptr;
};

与此非常相似的东西,std::observer_ptr(“世界上最愚蠢的智能指针”)是为 C++17 提出的,但我不确定它的状态。

【讨论】:

  • 我非常喜欢 unowned_ptr 的想法,但我认为按照您的建议,我将只使用原始指针并相信人们不会做愚蠢的事情。谢谢。
  • ““我将原始指针用于非拥有关系。我实际上喜欢它们。而且我不觉得它们困难或容易出错。” - Howard Hinnant。很高兴看到偶尔的答案脱离了智能指针货物的狂热。
  • @NicolasHolthaus 我不确定 Howard Hinnant 认为简单的事情是否适用于我们其他人!就个人而言,我可以理解observer_ptr 背后的动机,因为它允许我们说,对于新项目,“永远不要使用原始指针”,就像make_unique() 让我们说“永远不要使用new”一样。它是否会流行还有待观察。
猜你喜欢
  • 1970-01-01
  • 2017-12-22
  • 2013-08-07
  • 2014-11-06
  • 1970-01-01
  • 2017-01-15
  • 1970-01-01
  • 2021-06-08
  • 2022-01-20
相关资源
最近更新 更多