C++ SharedPtr 智能指针操作二维数组的细节异同

来源:互联网 发布:联想数据恢复中心 编辑:程序博客网 时间:2024/06/06 01:35

智能指针(以shared_ptr为代表)是现代C++中非常重要的STL类。其使用引用计数机制,能够自动释放内存,从而一定程度上避免了内存泄漏。然而,由于STL库的版本各不相同,不同的shared_ptr实现还是会对使用造成影响。近期,笔者在升级维护一个既有代码的二维数组动态管理时,遇到了一类特殊的情况,特此记录。

二维数组智能指针的编译错误

待升级的源代码一直被认为健壮性很棒,用 GNU C++ 4、Visual C++ 2013 编译时,几乎0警告通过。其使用了大量的shared_ptr在多个线程中传递整块的数据,在7*24小时运行中表现出很强的稳定性,其中一段代码如下:

bool foo (const int N, ...){\\...    shared_ptr< int[24] > bar(new int[N][24],        [=](int(*p)[24])->void { delete[] p; }        );}

foo函数中,定义了一个名为bar的智能指针,指向一个二维数组。第二维包含24个元素,第一维是动态管理的。这段代码很清晰,模板类型为 int[24],释放器的类型为 int(*)[24]。

可是,当升级到GNU C++ 7.2时,情况发生了变化:

error: no matching function for call to 'std::shared_ptr<int [24]>::shared_ptr(int (*)[24])'  shared_ptr< int [24] > pt (new int [4096][24]);

这真是非常奇怪!相同的代码,在Visual C++ 2017编译器下、GNU-C++ 4编译器下均是正常的。

shared_ptr实现异同

为了找到原因,我们首先看看成功编译通过的版本。
GNU C++ 4 附带实现:

   template<typename _Tp>    class shared_ptr : public __shared_ptr<_Tp>    {    public:      constexpr shared_ptr() noexcept      : __shared_ptr<_Tp>() { }      shared_ptr(const shared_ptr&) noexcept = default;      template<typename _Tp1>    explicit shared_ptr(_Tp1* __p)        : __shared_ptr<_Tp>(__p) { }      template<typename _Tp1, typename _Deleter>    shared_ptr(_Tp1* __p, _Deleter __d)        : __shared_ptr<_Tp>(__p, __d) { }    };

Visual C++ 2017 附带实现:

template<class _Ty>    class shared_ptr        : public _Ptr_base<_Ty>    {   // class for reference counted resource managementpublic:    typedef shared_ptr<_Ty> _Myt;    typedef _Ptr_base<_Ty> _Mybase;    template<class _Ux>        explicit shared_ptr(_Ux *_Px)        {   // construct shared_ptr object that owns _Px        _Resetp(_Px);        }    template<class _Ux,        class _Dx>        shared_ptr(_Ux *_Px, _Dx _Dt)        {   // construct with _Px, deleter        _Resetp(_Px, _Dt);        }    }

而编译出现错误的 GNUC++ 7.2附带 STL实现版本如下:

  template<typename _Tp>    class shared_ptr : public __shared_ptr<_Tp>    {    public:    template<typename _Yp, typename = _Constructible<_Yp*>>    explicit    shared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }    template<typename _Yp, typename _Deleter,           typename = _Constructible<_Yp*, _Deleter>>    shared_ptr(_Yp* __p, _Deleter __d)        : __shared_ptr<_Tp>(__p, std::move(__d)) { }

该定义牵扯到一系列的问题,主要是存在于复杂的typename模板操作上。貌似 类型 int[24] 不是一种具有一般可构造特性的类型。但是由于该版本STL实现非常复杂,暂时没有时间阅读,只能通过折中的办法来处理:降低编译器版本。

C++语言标准的碎碎念

本人熟练使用C/C++语言已经至少15年,目睹了C++语言的不断变化,尤其是近几年,很多激进的特性被引入进来。有时觉得,C++标准委员会对这种语言的发展,有点剑走偏锋了。这种语言的精髓是在于强大的无所不包的类库。从图像处理到视频剪辑,从信号处理到模式识别,开源的资源包罗万象。标准委员会最应该把精力投入到支持现代化类库发展上去,比如在语言周边,设立一两个标准框架库的名分(如Qt、boost);其次,应该详细的测试并审核各种代码片段的行为,让不同的编译器的编译行为、产生的代码行为尽可能一致。解决了这两个问题,再去研究拓展语言特性,就游刃有余了。

不知道委员会了不了解,很多非计算机专业的工程师从不自己写类。他们只是把C++标准、准标准(如大公司的类库)提供的类库当做int, double 一样的简单类型来用而已。正如一个嵌入式工程师,可能使用整套Qt类库,但仍旧采用面向过程的风格完成串口继电器控制一样,他们其实是用C++的便捷类库继续开发C程序。因此,语言的编译器一致性是最关键的,而不是能不能写出酷炫的代码。


这应该是个问题,已经提交了一个maillist给GCC
https://gcc.gnu.org/ml/libstdc++/2017-10/msg00020.html
希望后续有结果

最终回复

很快收到了答复:

No, it's because in C++11 and C++14 shared_ptr was not designed to beused with arrays. In C++17 arrays are fully supported, but in a waythat is incompatible with your code.It will work if you use shared_ptr<int[]24]> because that's actually ashared_ptr that owns a 2D array.

我们采用下面的代码,就编译通过了:

    shared_ptr< int[][24] > pt (                new int[4096][24],            [=](int (*p)[24])->void{delete [] p;}    );
原创粉丝点击