[zz]shared_ptr 在 stl容器中排序的陷阱

实例代码:

// test1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <string.h>
#include <string>
#include <memory>
#include <list>
#include <iostream>



using namespace  std;
using namespace  std::tr1;

class CInt: public enable_shared_from_this<CInt>
{
public:
    CInt( int _value):value_(_value){}
    ~CInt(){}

    int GetValue() { return value_;}

    bool operator==( const CInt & other ) const 
    {
        return value_ == other.value_;
    }

    bool operator < ( const CInt & other ) const 
    {
        return value_ < other.value_;
    }
protected:
private:
    int value_;
};


bool SPIntCmp( shared_ptr<CInt> & c1, shared_ptr<CInt> & c2)
{
    return c1 < c2;
}

bool SPIntValueCmp( shared_ptr<CInt> & c1, shared_ptr<CInt> & c2)
{
    return *c1 < *c2;
}


int _tmain(int argc, _TCHAR* argv[])
{
    
    list< shared_ptr<CInt> > ilist;

    ilist.push_back( shared_ptr<CInt>( new CInt(1)));
    ilist.push_back( shared_ptr<CInt>( new CInt(3)));
    ilist.push_back( shared_ptr<CInt>( new CInt(5)));
    ilist.push_back( shared_ptr<CInt>( new CInt(7)));
    ilist.push_back( shared_ptr<CInt>( new CInt(9)));
    ilist.push_back( shared_ptr<CInt>( new CInt(2)));
    ilist.push_back( shared_ptr<CInt>( new CInt(4)));
    ilist.push_back( shared_ptr<CInt>( new CInt(6)));
    ilist.push_back( shared_ptr<CInt>( new CInt(8)));
    ilist.push_back( shared_ptr<CInt>( new CInt(10)));

    cout<<"Before sort"<<endl;
    for ( auto it = ilist.begin(); it != ilist.end(); it++)
    {
        shared_ptr<CInt> i = *it;
        cout<< i->GetValue()<<" ";
    }
    cout<<endl;

    ilist.sort();
    cout<<"after default sort"<<endl;
    for ( auto it = ilist.begin(); it != ilist.end(); it++)
    {
        shared_ptr<CInt> i = *it;
        cout<< i->GetValue()<<" ";
    }

    cout<<endl;
    ilist.sort(SPIntCmp);
    cout<<"after SPIntCmp sort"<<endl;
    for ( auto it = ilist.begin(); it != ilist.end(); it++)
    {
        shared_ptr<CInt> i = *it;
        cout<< i->GetValue()<<" ";
    }
    cout<<endl;
    ilist.sort(SPIntValueCmp);
    cout<<"after SPIntValueCmp sort"<<endl;
    for ( auto it = ilist.begin(); it != ilist.end(); it++)
    {
        shared_ptr<CInt> i = *it;
        cout<< i->GetValue()<<" ";
    }
    cout<<endl;

    return 0;
}

输出:

Before sort
1 3 5 7 9 2 4 6 8 10
after default sort
9 2 4 6 8 10 1 3 5 7
after SPIntCmp sort
9 2 4 6 8 10 1 3 5 7
after SPIntValueCmp sort
1 2 3 4 5 6 7 8 9 10
请按任意键继续. . .


从结果可以看到调用 list.sort() 和list.sort(SPIntCmp)方法输出结果是一样的。都是错误的结果。

只有调用list.sort(SPIntValueCmp)的输出结果还是真确的。

这是因此 list的sort()是调用容器对象的 operator<方法,容器对象是什么?这里容器对象是 shared_ptr<CInt> 而不是CInt,因此并不会调用 CInt的operator <方法进行比较,而是调用shared_ptr<CInt>的operator<方法进行比较。查看shared_ptr源代码 后,发现 shared_ptr的默认实现方式是根据资源的指针地址进行比较的,因此我们无法得到正确的排序结果。

对于此事,我们必须定制自己的Compare方法。在SPIntCmp方法中,我们调用的还是 shared_ptr的operator <方法,因此效果和list.sort()的结果是一样的。

在SPIntValueCmp中,我们使用的是shared_ptr管理的资源的值进行比较(这边代码省略了null ptr的判断处理,实际代码需要加上),此事调用的才是 CInt对象的operator<方法,因此能得到正确的结果。


如果 list<CInt *> 的sort()结果应该和list<shared_ptr<CInt> >的结果都是一样不可预料的。


根本原因是shared_ptr是按照引用来比较的而不是引用的值进行比较的,因此在使用shared_ptr时,对于在逻辑语意上相等的对象,一定要从同一个数据中构造shared_ptr对象,而不要从一个新的一模一样的副本中构造shared_ptr

例如

shared_ptr<CInt> c1( new CInt(1) );

shared_ptr<CInt> c1_1( new CInt(1));

对 c1 == c1_1 的结果是 false,而不是true,而 c1 < c1_1 结果并不一定是false,而是由CInt对象的地址决定,因此他们的引用不是同一个对象,虽然他们的引用是相等的2个对象。应该像下面这样使用

shared_ptr<CInt> c1( new CInt(1) );

shared_ptr<CInt> c1_1( c1

);

是否感觉到shared_ptr很像Java的Object对象。Java对象默认也是根据引用来比较的,如果要根据对象的语意进 行相等比较必须重载 equal方法。可惜shared_ptr没有提供这样的方式让我们可以直接比较shared_ptr受控对象的语意相等性。只能选择根据地址来比较。

原文地址:https://www.cnblogs.com/zhangzhang/p/2877401.html