【问题标题】:Is there a better/safe way to cast non-const reference of shared_ptr to a base class?有没有更好/安全的方法将 shared_ptr 的非常量引用转换为基类?
【发布时间】:2016-10-08 00:24:09
【问题描述】:

如果你有一个带有虚拟方法的类 Base 和一个实现虚拟方法的类 Implementation,有没有办法将 std::shared_ptr & 转换为 std::shared &?对于 const 引用,编译器允许这样做,但对于非 const 引用,它会像下面代码中的“案例 A”一样失败。有没有简单的方法可以做到这一点?

如果不是,我在案例 B 中的解决方法“questionable_cast”的安全性如何?

#include <iostream>
#include <memory>

class Base
{
public:
    virtual void set_value(int x) = 0;
};

class Implementation : public Base
{
public:
    Implementation() : m_value(0) { }
    void set_value(int x) override
    {
    m_value = x;
    }
    int get_value() const
    {
    return m_value;
    }
private:
    int m_value;
};


void do_something(std::shared_ptr<Base>& base)
{
    base->set_value(5);

    /// Code like this makes the non-const argument necessary
    base = std::make_shared<Implementation>();
}

template <class T, class U>
std::shared_ptr<T>& questionable_cast(std::shared_ptr<U>& u)
{
    /// This code is here to assure the cast is allowed
    std::shared_ptr<T> tmp = u;
    (void)tmp;

    return *reinterpret_cast<std::shared_ptr<T>*>(&u);
}

int main()
{
    std::shared_ptr<Implementation> a = std::make_shared<Implementation>();

    // The following line causes a compiler error:
    //  invalid initialization of reference of type ‘std::shared_ptr<Base>&’ ...
    // do_something(a);
    // do_something(std::dynamic_pointer_cast<Base>(a));

    // This is the workaround
    do_something(questionable_cast<Base>(a));

    std::cerr << "a = " << a->get_value() << std::endl;

    return 0;
}

【问题讨论】:

  • 额外问题:为什么这被否决了?
  • 您为什么要尝试使用参考来执行此操作,而不是像在 questionable_cast 函数中那样制作副本?使用引用不会增加计数器,并使引用无效。
  • 我没有投反对票,但可能的原因是:为什么你想这样做?像这样使用 reinterpret_cast 必然会违反严格的别名规则,从而导致未定义的行为。
  • 我更新了代码,以便您可以看到为什么参数需要是非常量引用。相信我,我不想使用 reinterpret_cast。这就是为什么我要问是否有办法解决它
  • 为什么Derived*&amp;(或Derived**)不能转换为Base*&amp;(或Base**)肯定有几个答案,这是同一个问题。简而言之:它不是类型安全的。 (考虑将指向不同派生类的指针传递给do_something 的情况。)

标签: c++ c++11 casting shared-ptr


【解决方案1】:

最初提出的问题的两个明显解决方案: 1. 使do_something 对 shared_ptr (或按值的 shared_ptr )进行 const 引用。 2. 创建一个名为 shared_ptr 并传递对其的引用:例如

int main()
{
    std::shared_ptr<Implementation> a = std::make_shared<Implementation>();
    std::shared_ptr<Base> b = a;  // This conversion works.
    do_something(b);  // Pass a reference to b instead.
    return 0;
}

您的questionable_cast 函数违反了严格的别名规则,并调用了未定义的行为。它很可能在初始测试中起作用,然后新版本的编译器会将优化提升一个档次,但在演示期间它会失败。

处理do_something改变指针的情况:

int main()
{
    std::shared_ptr<Implementation> a = std::make_shared<Implementation>();
    std::shared_ptr<Base> b = a;  // This conversion works.
    do_something(b);  // Pass a reference to b instead.
    const auto aa = std::dynamic_pointer_cast<Implementation>(b);
    if (aa)
        a = aa;
    else
        ; // Handle the error here
    return 0;
}

如果do_something 保证返回相同派生类型的指针,即使它不返回相同的指针,也将其包装在模板函数中:

template <typename T>
void do_something_ex( std::shared_ptr<T>& a )
{
    std::shared_ptr<Base> b = a;
    do_something(b)
    a = std::dynamic_pointer_cast<T>(b);
    if (!a)
        throw_or_assert;
}

【讨论】:

  • 好的。对不起。我忘记了 do_something 的一个重要特征。它也可能改变 shared_ptr。那么你的任何建议都行不通。
  • @bofjas:还是可以的,这样的改动会反映在b。如果您希望它们反映到 a,然后将 b 复制回其中。
  • 是的,我不想这样做的原因是因为我需要做一个 dynamic_pointer_cast 并检查结果,所以我调用函数的每个地方都会有很多代码,但我没有意识到这实际上是在告诉我我的方法有什么不安全的地方。猜猜我坚持这个解决方案。不过感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 2014-11-09
  • 1970-01-01
  • 2016-01-13
  • 2014-07-27
  • 1970-01-01
  • 2022-09-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多