Как превратить реляционное сравнение указателей в ошибку? - PullRequest
10 голосов
/ 09 июля 2010

Мы неоднократно кусали следующую ошибку:

#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 сравнивает целочисленные указатели, а не целые числа, а это не то, что задумал программист. Хуже того, выходные данные могут казаться правильными и детерминированными (учитывайте порядок адресов, возвращаемых новыми или размещенными в стеке). Основная проблема заключается в том, что сортировка в конечном итоге вызывает operator <для T, что редко бывает хорошей идеей, когда T является типом указателя. </p>

Есть ли способ предотвратить это или хотя бы получить предупреждение компилятора? Например, есть ли способ создать собственную версию std :: sort, для которой требуется функция сравнения, когда T является указателем?

Ответы [ 3 ]

12 голосов
/ 09 июля 2010

ИМО, программисты должны знать, что 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>);

Редактировать

FWIW, Ответ Джейкоба наиболее близок к выполнению того, что вы хотите.Может быть несколько способов обобщить это далее.

2 голосов
/ 09 июля 2010

Для указателей в целом вы могли бы сделать это:

    #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.Я уверен, что кто-то сможет написать более красивое решение, но это, кажется, работает.

Но я должен сказать, что я согласен с зубчатым колесом, нет никаких причин для этого, программист должен видетьесли это будет проблемой, и действуйте соответственно.

Добавление:

Я думаю, вы можете обобщить это немного больше, чтобы автоматически выбрать функтор, который будет разыменовывать указатели и сравнивать значения:

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() );
    }
}

Таким образом, вам не нужно думать о том, если вы предоставите ему указатели для сравнения или нет, он будет автоматически отсортирован.

2 голосов
/ 09 июля 2010

У меня нет хорошего ответа для указателей в целом, но вы можете ограничить сравнение, если вы используете какой-либо умный указатель - например, 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

Это все же требует, чтобы ваши программисты использовали его, конечно!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...