Sorting a vector of custom objects

后端 未结 13 2989
既然无缘
既然无缘 2020-11-21 05:14

How does one go about sorting a vector containing custom (i.e. user defined) objects.
Probably, standard STL algorithm sort along with a predicate (a fu

13条回答
  •  遥遥无期
    2020-11-21 05:36

    Sorting such a vector or any other applicable (mutable input iterator) range of custom objects of type X can be achieved using various methods, especially including the use of standard library algorithms like

    • sort,
    • stable_sort,
    • partial_sort or
    • partial_sort_copy.

    Since most of the techniques, to obtain relative ordering of X elements, have already been posted, I'll start by some notes on "why" and "when" to use the various approaches.

    The "best" approach will depend on different factors:

    1. Is sorting ranges of X objects a common or a rare task (will such ranges be sorted a mutiple different places in the program or by library users)?
    2. Is the required sorting "natural" (expected) or are there multiple ways the type could be compared to itself?
    3. Is performance an issue or should sorting ranges of X objects be foolproof?

    If sorting ranges of X is a common task and the achieved sorting is to be expected (i.e. X just wraps a single fundamental value) then on would probably go for overloading operator< since it enables sorting without any fuzz (like correctly passing proper comparators) and repeatedly yields expected results.

    If sorting is a common task or likely to be required in different contexts, but there are multiple criteria which can be used to sort X objects, I'd go for Functors (overloaded operator() functions of custom classes) or function pointers (i.e. one functor/function for lexical ordering and another one for natural ordering).

    If sorting ranges of type X is uncommon or unlikely in other contexts I tend to use lambdas instead of cluttering any namespace with more functions or types.

    This is especially true if the sorting is not "clear" or "natural" in some way. You can easily get the logic behind the ordering when looking at a lambda that is applied in-place whereas operator< is opague at first sight and you'd have to look the definition up to know what ordering logic will be applied.

    Note however, that a single operator< definition is a single point of failure whereas multiple lambas are multiple points of failure and require a more caution.

    If the definition of operator< isn't available where the sorting is done / the sort template is compiled, the compiler might be forced to make a function call when comparing objects, instead of inlining the ordering logic which might be a severe drawback (at least when link time optimization/code generation is not applied).

    Ways to achieve comparability of class X in order to use standard library sorting algorithms

    Let std::vector vec_X; and std::vector vec_Y;

    1. Overload T::operator<(T) or operator<(T, T) and use standard library templates that do not expect a comparison function.

    Either overload member operator<:

    struct X {
      int i{}; 
      bool operator<(X const &r) const { return i < r.i; } 
    };
    // ...
    std::sort(vec_X.begin(), vec_X.end());
    

    or free operator<:

    struct Y {
      int j{}; 
    };
    bool operator<(Y const &l, Y const &r) { return l.j < r.j; }
    // ...
    std::sort(vec_Y.begin(), vec_Y.end());
    

    2. Use a function pointer with a custom comparison function as sorting function parameter.

    struct X {
      int i{};  
    };
    bool X_less(X const &l, X const &r) { return l.i < r.i; }
    // ...
    std::sort(vec_X.begin(), vec_X.end(), &X_less);
    

    3. Create a bool operator()(T, T) overload for a custom type which can be passed as comparison functor.

    struct X {
      int i{};  
      int j{};
    };
    struct less_X_i
    {
        bool operator()(X const &l, X const &r) const { return l.i < r.i; }
    };
    struct less_X_j
    {
        bool operator()(X const &l, X const &r) const { return l.j < r.j; }
    };
    // sort by i
    std::sort(vec_X.begin(), vec_X.end(), less_X_i{});
    // or sort by j
    std::sort(vec_X.begin(), vec_X.end(), less_X_j{});
    

    Those function object definitions can be written a little more generic using C++11 and templates:

    struct less_i
    { 
        template
        bool operator()(T&& l, U&& r) const { return std::forward(l).i < std::forward(r).i; }
    };
    

    which can be used to sort any type with member i supporting <.

    4. Pass an anonymus closure (lambda) as comparison parameter to the sorting functions.

    struct X {
      int i{}, j{};
    };
    std::sort(vec_X.begin(), vec_X.end(), [](X const &l, X const &r) { return l.i < r.i; });
    

    Where C++14 enables a even more generic lambda expression:

    std::sort(a.begin(), a.end(), [](auto && l, auto && r) { return l.i < r.i; });
    

    which could be wrapped in a macro

    #define COMPARATOR(code) [](auto && l, auto && r) -> bool { return code ; }
    

    making ordinary comparator creation quite smooth:

    // sort by i
    std::sort(v.begin(), v.end(), COMPARATOR(l.i < r.i));
    // sort by j
    std::sort(v.begin(), v.end(), COMPARATOR(l.j < r.j));
    

提交回复
热议问题