如何将指针的关系比较转化为错误?

10

我们多次遭受以下错误的困扰:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void print(int* pn) { cout << *pn << " "; }

int main() {
    int* n1 = new int(1);
    int* n2 = new int(2);
    int* n3 = new int(3);

    vector<int*> v;
    v.push_back(n1);
    v.push_back(n2);
    v.push_back(n3);

    sort(v.begin(), v.end());   // Here be dragons!

    for_each(v.begin(), v.end(), print);
    cout << endl;
    delete n1; delete n2; delete n3;
}

问题在于std::sort比较的是整数指针而不是整数,这不是程序员想要的。更糟糕的是,输出可能看起来正确和确定性(考虑由new返回或在堆栈上分配的地址顺序)。根本问题在于sort最终调用T的operator<,当T是指针类型时,这很少是一个好主意。
有没有办法防止这种情况或者至少得到编译器警告?例如,是否有一种方法可以创建一个自定义版本的std::sort,当T是指针时需要比较函数?

1
我们都被这个 bug 咬过很多次。这就是为什么我对这个问题感兴趣的原因。虽然我不认为有人真正解决了它。我们需要的是一些东西来确保这种情况不会编译,这样当你在熬夜之后再次编写代码时,它就不会留在代码中了。 - Brian Hooper
每次我试图想出一些有用的话来说,最终都会归结为“让开发人员变得更聪明”。然后我意识到,像这样简单的事情也会发生在聪明的开发人员身上。一定要记录(用维基百科?)关于你的代码中出现的最严重的错误,并在代码审查期间特别注意它们。对于初级开发人员要加倍注意。 - corsiKa
3个回答

12

在我看来,程序员应该知道std::sort假定容器存储的是值。如果您需要不同的比较行为,则可以提供一个比较函数。例如(未经测试):

template<typename T>
inline bool deref_compare(T* t1, T* t2) { return *t1 < *t2; }

//...

std::sort(v.begin(), v.end(), deref_compare<int>);

编辑

就你的需求而言,Jacob的回答最接近直接实现。可能还有一些更进一步泛化的方法。


很抱歉进行了多次编辑,我一直以为它正在工作,但实际上并非如此。我现在应该坚持我的原始答案。 - Cogwheel
问题在于有时开发人员会忘记提供比较函数。例如,他们将容器修改为按指针存储而不是按值存储,但却忘记更新所有的排序调用。 - Marc Eaddy
是的,这就是为什么我支持Jacob的帖子。 :) 顺便说一句,我不禁想知道,你是否会像提醒人们在使用时要更加小心一样经常提醒他们不要经常使用std::sort ;) - Cogwheel

2

我对指针并没有一个很好的答案,但是如果你正在使用任何智能指针-例如boost::shared_ptr,那么你可以限制比较。

#include <boost/shared_ptr.hpp>
using namespace std;

template<class T>
bool operator<(boost::shared_ptr<T> a, boost::shared_ptr<T> b)
{
  return boost::shared_ptr<T>::dont_compare_pointers;
}

int main () {
  boost::shared_ptr<int> A;
  boost::shared_ptr<int> B;
  bool i = A < B;  
}

输出:

In function 'bool operator<(boost::shared_ptr<T>, boost::shared_ptr<T>) [with T = int]':
t.cpp:15:   instantiated from here
Line 8: error: 'dont_compare_pointers' is not a member of 'boost::shared_ptr<int>'
compilation terminated due to -Wfatal-errors.

因此,您可以使用智能指针或创建自己的智能指针包装器。但是,这对于您想要的内容来说非常笨重,因此,如果您创建一个包装器来检测此情况,我建议您仅在调试模式下使用它。因此,请创建一个宏(呃,我知道)并使用它声明指针。

#ifdef DEBUG
    #define pointer(x) pointer_wrapper<X>
#else
    #define pointer(x) x*
#endif

当然,这仍需要你的程序员使用它!

2

对于指针,你可以这样做:

    #include <ctime>
    #include <vector>
    #include <cstdlib>
    #include <algorithm>
    #include <functional>
    #include <type_traits>

    namespace util {
        struct sort_pointers {
            bool operator() ( int *a, int *b ) {
                return *a < *b;
            }
        };

        template <typename T, bool is_pointer = !std::tr1::is_pointer<T>::value>
        struct sort_helper {
            typedef std::less<T> wont_compare_pointers;
        };

        template <typename T>
        struct sort_helper<T,false> {
        };

        template <typename Iterator>
        void sort( Iterator start, Iterator end )
        {
            std::sort( start,
                       end,
                       sort_helper
                       <
                            typename Iterator::value_type
                       >::wont_compare_pointers() );
        }

        template <typename Iterator, class Func>
        void sort( Iterator start, Iterator end, Func f ) {
            std::sort( start, end, f );
        }
    }

    int main() {
        std::vector<int> v1;
        std::vector<int*> v2;
        srand(time(0));

        for( int i = 0; i < 10; ++i ) {
            v1.push_back(rand());
        }

        util::sort( v1.begin(), v1.end() );

        for( int i = 0; i < 10; ++i ) {
            v2.push_back(&v1[i]);
        }

        /* util::sort( v2.begin(), v2.end() ); */ //fails.
        util::sort( v2.begin(), v2.end(), util::sort_pointers() );

        return 0;
    }

std::tr1::is_pointer在Visual Studio 2008中就是这样称呼的,但我认为Boost也有一个类似的功能,而新的编译器可能会提供std::is_pointer。我相信有人能够写出更漂亮的解决方案,但这个方法似乎有效。

但我必须说,我同意cogwheel的观点,没有理由这样做,程序员应该能够看到这是否会成为问题并采取相应措施。

补充:

我认为可以将其更加通用化,自动选择一个函数对象来取消引用指针并比较值:

namespace util {
    template <typename T>
    struct sort_pointers {
        bool operator() ( T a, T b ) {
            return *a < *b;
        }
    };

    template <typename T, bool is_pointer = !std::tr1::is_pointer<T>::value>
    struct sort_helper {
        typedef std::less<T> compare;
    };

    template <typename T>
    struct sort_helper<T,false> {
        typedef sort_pointers<T> compare;
    };

    template <typename Iterator>
    void sort( Iterator start, Iterator end )
    {
        std::sort( start,
                   end,
                   sort_helper
                   <
                        typename Iterator::value_type
                   >::compare() );
    }
}

这样你就不必考虑是否提供了指针来进行比较,它会自动排序。


非常好!我修改了sort_pointers :: op <,使其返回“std :: dont_compare_pointers”,类似于Nicholas,这样开发人员就会得到编译错误,并被迫提供比较器。现在我需要创建一个修改过的STL版本,以便对所有比较(排序,map :: insert等)使用此技巧。 - Marc Eaddy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接