【问题标题】:Understanding the prototype of shared_ptr aliasing constructor理解 shared_ptr 别名构造函数的原型
【发布时间】:2018-02-25 00:12:16
【问题描述】:

g++ 形式的 shared_ptr 别名构造函数的原型:

  template<typename _Yp>
    shared_ptr(const shared_ptr<_Yp>& __r, element_type* __p) noexcept
    : __shared_ptr<_Tp>(__r, __p) { }

这里给出的例子是:

shared_ptr< pair<int,int> > pii(new pair<int,int>());
shared_ptr<int> pi(pii, &pii->first);

这行得通;一直有效。但是看看原型,_Yp 是我们为实例化模板而提供的模板参数,因此上面的最后一行感觉应该是:

shared_ptr<pair> pi(pii, &pii->first);

但绝对这个例子是正确的。那么我们该如何解释呢?我今天第一次看原型,我试图理解如何解释它。感谢您的 cmets/解释。

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    您混淆了不同的模板参数/参数。 _Yp 不是我们用来实例化 shared_ptr 的模板参数。在GCC的实现中整个shared_ptr模板的参数叫做_Tp,而不是_Yp。在shared_ptr 内部,_Tp 也称为element_type

    同时,_Yp 是嵌套的成员模板的参数,它是构造函数模板。

    shared_ptr 本身和它的构造函数模板是两个“正交”模板。 _Tp_Yp 是两个独立且不相关的模板参数。

    您没有(也不能)明确指定_Yp 的参数。会自动推导出来的。但是您必须为_Tp 指定参数,这正是您在示例中看到的

    shared_ptr<int> pi(pii, &pii->first);
               ^      ^
               |      |
               |      The `_Yp` parameter is kinda/sorta implicitly present here.
               |      It parametrizes the constructor template. C++ has no syntax
               |      for specifying it explicitly
               |
               This is `_Tp`, not `_Yp`. `_Tp` parametrizes 
               the whole `shared_ptr` template
    

    本例中的_Tp 应指定为int_Yp 是从 pii 推导出来的 pair&lt;int,int&gt;,完全符合您的预期。

    【讨论】:

    • 我很惊讶 C++17 中似乎没有演绎指南。但是,可以添加一个:namespace std { template&lt;typename T, typename Y&gt; shared_ptr(const shared_ptr&lt;Y&gt; &amp;, T *) -&gt; shared_ptr&lt;T&gt;; } - 不过,我不知道这是标准允许的还是一个好主意。我之前没有使用扣除指南的经验。
    【解决方案2】:

    短版

    别名构造函数可以指向任何类型的对象,因此您可以创建任何类型的别名 shared_ptr(理论上)。简而言之,引用计数与别名类型解耦。

    长版

    别名构造函数创建一个新的 shared_ptr,但重新使用控制块(它保存分配信息、引用计数等)。 &amp;pii-&gt;first 的结果是int*,所以应该是shared_ptr&lt;int&gt;,因为取消引用指针会返回int&amp;

    这也意味着别名构造函数没有关于释放或分配存储在别名指针中的类型的信息,这正是别名构造函数可能很危险的原因:你告诉用户它参与了引用计数,但是,有没有明确保证它会这样做。这可能会导致取消引用释放的内存或内存泄漏,并错误地延长原始 shared_ptr 的生命周期。在这种情况下,正确使用了别名构造函数(将构造对内的指针的生命周期与对本身的生命周期联系起来),但是,无论何时使用别名构造函数都应谨慎。您通常应该使用别名构造函数来创建指向集合(如对、元组、数组或结构)内的值的指针,以确保指针在新指针的持续时间内保持有效。

    使用不相关的值可能会出现严重错误的示例如下:

    #include <memory>
    #include <utility>
    
    using int_pair = std::pair<int, int>;
    
    // Since this aliases a local variable, which goes out-of-scope
    // immediately, dereferencing the variable will reference junk memory.
    // It will also increment the shared_count of `p`, which may lead
    // to a longer lifetime than desired.
    // (This is obviously wrong, and that's the point).
    std::shared_ptr<int> temporary_reference(std::shared_ptr<int_pair>& p)
    {
        int x = 5;
        return std::shared_ptr<int>(p, &x);
    }
    
    
    // Since this aliases a newly allocated variable, this will create a memory
    // leak, since `new int(5)` will never be properly deleted.
    // It will also increment the shared_count of `p`, which may lead
    // to a longer lifetime than desired.
    // (This is obviously wrong, and that's the point).
    std::shared_ptr<int> memory_leak(std::shared_ptr<int_pair>& p)
    {
        return std::shared_ptr<int>(p, new int(5));
    }
    
    
    // This references an internal value in `p` and ties the lifetime
    // of `p` to the aliased pointer, to ensure that deleting p
    // does not invalidate the aliased pointer.
    std::shared_ptr<int> aliased_value(std::shared_ptr<int_pair>& p)
    {
        return std::shared_ptr<int>(p, &p->first);
    }
    
    
    int main()
    {
        std::shared_ptr<int_pair> p(new int_pair(5, 3));
        auto i1 = temporary_reference(p);
        auto i2 = memory_leak(p);
        auto i3 = aliased_value(p);
        return 0;
    }
    

    如果我们使用指向&amp;p-&gt;first 的原始指针,而不是别名shared_ptr,并从外部删除对p 的引用,则指针可能会变得无效。使用别名构造函数可确保内部指针在别名 shared_ptr 的生命周期内有效。

    使用原始指针可能会出错的示例如下:

    #include <memory>
    #include <utility>
    
    using int_pair = std::pair<int, int>;
    
    int main()
    {
        std::shared_ptr<int_pair> p(new int_pair(5, 3));
        int& i = p->first;
        p.reset();
        // the reference to i is no longer valid, 
        // since the shared count of the control block of `p` is now 0.
        // if we had used an aliased shared_ptr, it still would be valid
    
        return 0;
    }
    

    【讨论】:

    • 您能否详细解释一下dangerouscaution 的含义?或许可以举一个出现问题的例子,即使代码看起来很无辜?
    • @Walter 好主意,我添加了两个示例。我会更新以解释谨慎和危险。
    • @Walter 这样更好吗?
    • 你的意思是你的第一个例子使用aliased_value 是危险的吗?前两个示例看起来很愚蠢,并且似乎不适合/有意使用别名构造函数。原始指针示例似乎根本没有使用别名构造函数。
    • 这就是前两个例子的重点。它们是微不足道的,但它们突出显示原始容器之外的混叠对象是危险的。 int main() 实际上是 C++ 标准的有效入口点。 int main(void) 应该被劝阻(类似 C),但 int main()int main(int argc, char **argv) 都是由标准保证的。
    猜你喜欢
    • 2015-04-20
    • 2020-05-31
    • 2017-08-21
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多