Overloading comparision operator in C++ results in

2019-02-09 14:28发布

问题:

Currently attempting to sort a vector of object, with each object containing a string, in C++

The strings can contain letters or numbers (due to a design constraint, this is necessary, as the comparator can be changed).

At the moment, the class of the object is overloaded, so that when two objects are compared, the strings they contain are compared. This works to a point -- however, when I use a sort operation (such as STL sort) to put the objects in order, it will sort three strings such as "1", "4", "12", in the order "1", "12", "4". 4 is greater then 12, but because it begins comparing from the most left digit, this 'incorrect' sort occurs.

My initial response was to change how I was overloading the comparison operation. I would first check the length of the string I was comparing -- which would be a telltale sign if the string's contents was larger or smaller.

// overloaded comparision operators
friend bool operator<(const nodeRecord & record1, const nodeRecord & record2){
    // we need to deal with strings of different lengths...
    if(record1.comparator.length() < record2.comparator.length())
        return true;
    else
        return (record1.comparator < record2.comparator);
}

This operation results in a "Expression: invalid operator<" message during runtime.

Any ideas as to where I am making a mistake? It seems that I should be able to dictate to the operation exactly how I want the sorting operation to occur -- even if it is invalid, since I am currently using a vector to contain the objects.

Comparator during initialization of the nodeRecord object:

nodeRecord(int fromNode, int toNode, int connectionCost, bool compareByCost = false){
    // take the provided stock information and insert it into the object
    stringstream fromNodeSS;
    fromNodeSS << fromNode;
    this->fromNode = fromNodeSS.str();
    stringstream toNodeSS;
    toNodeSS << toNode;
    this->toNode = toNodeSS.str();
    this->connectionCost = connectionCost;

    // set the comparator to our chosen comparision term
    if (!compareByCost){
        this->comparator = this->fromNode; // we use from node in this case, since we build the tree outwards
    }
    else{
        stringstream ss;
        ss << this->connectionCost;
        this->comparator = ss.str(); // we use the connection cost in this case, to allow us to sort new connections
    }

    // set this as a non-null (active) record
    this->nullRecord = false;
}

回答1:

You operator is effectively invalid.

The operator < must have a number of mathematical properties if you want it to be usable for sorting. One is the AntiSymmetry property:

x < y => !(y < x)

Let's define x = "b" and y = "aa".

  • x < y because the length of "b" is inferior to the length of "aa"
  • y < x because "aa" is inferior to "b"

Hum ?

Also note that your definition is weird for numbers in case they are prefixed by 0s.

Oh, and comparing strings is way slower than comparing numbers.

My take ? Stop altering the node with comparison information. The actual comparison mode has nothing to do within the node itself.

Then you'll just write two comparison methods, one that compares by cost and the other by origin.


And to come back to the original issue, how to write a comparator that consider ["a", "b", "aa"] sorted ?

You were almost there, but the "length" comparison is incomplete. You need to fall back to the actual lexical comparison only in the case the lengths differ, thus you forgot the case where the right hand side argument's length is inferior to the left hand side's one.

Thus the correct form is, supposing two strings:

bool compare(std::string const& lhs, std::string const& rhs) {
  if (lhs.length() < rhs.length()) { return true; }
  if (rhs.length() < lhs.length()) { return false; } // don't forget this
  return lhs < rhs;
}


回答2:

Found the following code segment which was throwing the error, then thought about how my overloaded operation was working.

template<class _Ty1, class _Ty2> inline
    bool _Debug_lt(_Ty1& _Left, _Ty2& _Right,
        _Dbfile_t _File, _Dbline_t _Line)
    {   // test if _Left < _Right and operator< is strict weak ordering
    if (!(_Left < _Right))
        return (false);
    else if (_Right < _Left)
        _DEBUG_ERROR2("invalid operator<", _File, _Line);
    return (true);
    }

Working solution is this (modified again thanks to comments left by Matthieu M.)

// overloaded comparision operators
friend bool operator<(const nodeRecord & record1, const nodeRecord & record2){
    // we need to deal with strings of different lengths...
    if(record1.comparator.length() > record2.comparator.length()
        && (record1.comparator.length() !=0 && record2.comparator.length() != 0))
        return false;
    else if(record1.comparator.length() < record2.comparator.length()
        && (record1.comparator.length() !=0 && record2.comparator.length() != 0))
        return true;
    else
        return (record1.comparator < record2.comparator);
}

Thanks to everyone who helped!



回答3:

Why don't you use a single comparator and make that function a little smarter? Have it check for numeric characters at the beginning, if so, do a pair of strtol() or atoi() and compare the results.

Otherwise compare the length of string and the characters as per your non-numeric requirements.