问题
Herb Sutter, in his proposal for the "spaceship" operator (section 2.2.2, bottom of page 12), says:
Basing everything on
<=>
and its return type: This model has major advantages, some unique to this proposal compared to previous proposals for C++ and the capabilities of other languages:[...]
(6) Efficiency, including finally achieving zero-overhead abstraction for comparisons: The vast majority of comparisons are always single-pass. The only exception is generated
<=
and>=
in the case of types that support both partial ordering and equality. For<
, single-pass is essential to achieve the zero-overhead principle to avoid repeating equality comparisons, such as forstruct Employee { string name; /*more members*/ };
used instruct Outer { Employeee; /*more members*/ };
– today’s comparisons violates zero-overhead abstraction becauseoperator<
onOuter
performs redundant equality comparisons, because it performsif (e != that.e) return e < that.e;
which traverses the equal prefix ofe.name
twice (and if the name is equal, traverses the equal prefixes of other members ofEmployee
twice as well), and this cannot be optimized away in general. As Kamiński notes, zero-overhead abstraction is a pillar of C++, and achieving it for comparisons for the first time is a significant advantage of this design based on<=>
.
But then he gives this example (section 1.4.5, page 6):
class PersonInFamilyTree { // ...
public:
std::partial_ordering operator<=>(const PersonInFamilyTree& that) const {
if (this->is_the_same_person_as ( that)) return partial_ordering::equivalent;
if (this->is_transitive_child_of( that)) return partial_ordering::less;
if (that. is_transitive_child_of(*this)) return partial_ordering::greater;
return partial_ordering::unordered;
}
// ... other functions, but no other comparisons ...
};
Would define operator>(a,b)
as a<=>b > 0
not lead to large overhead? (though in a different form than he discusses). That code would first test for equality, then for less
, and finally for greater
, rather than only and directly testing for greater
.
Am I missing something here?
回答1:
Would define
operator>(a,b)
asa<=>b > 0
not lead to large overhead?
It would lead to some overhead. The magnitude of the overhead is relative, though - in situations when costs of running comparisons are negligible in relation to the rest of the program, reducing code duplication by implementing one operator instead of five may be an acceptable trade-off.
However, the proposal does not suggest removing other comparison operators in favor of <=>
: if you want to overload other comparison operators, you are free to do it:
Be general: Don’t restrict what is inherent. Don’t arbitrarily restrict a complete set of uses. Avoid special cases and partial features. – For example, this paper supports all seven comparison operators and operations, including adding three-way comparison via
<=>
. It also supports all five major comparison categories, including partial orders.
回答2:
For some definition of large. There is overhead because in a partial ordering, a == b
iff a <= b
and b <= a
. The complexity would be the same as a topological sort, O(V+E)
. Of course, the modern C++ approach is to write safe, clean and readable code and then optimizing. You can choose to implement the spaceship operator first, then specialize once you determine performance bottlenecks.
回答3:
Generally speaking, overloading <=>
makes sense when you're dealing with a type where doing all comparisons at once is either only trivially more expensive or has the same cost as comparing them differently.
With strings, <=>
seems more expensive than a straight ==
test, since you have to subtract each pair of two characters. However, since you already had to load each pair of characters into memory, adding a subtraction on top of that is a trivial expense. Indeed, comparing two numbers for equality is sometimes implemented by compilers as a subtraction and test against zero. And even for compilers that don't do that, the subtract and compare against zero is probably not significantly less efficient.
So for basic types like that, you're more or less fine.
When you're dealing with something like tree ordering, you really need to know up-front which operation you care about. If all you asked for was ==
, you really don't want to have to search the rest of the tree just to know that they're unequal.
But personally... I would never implement something like tree ordering with comparison operators to begin with. Why? Because I think that comparisons like that ought to be logically fast operations. Whereas a tree search is such a slow operation that you really don't want to do it by accident or at any time other than when it is absolutely necessary.
Just look at this case. What does it really mean to say that a person in a family tree is "less than" another? It means that one is a child of the other. Wouldn't it be more readable in code to just ask that question directly with is_transitive_child_of
?
The more complex your comparison logic is, the less likely it is that what you're doing is really a "comparison". That there's probably some textual description that this "comparison" operation could be called that would be more readable.
Oh sure, such a class could have a function that returns a partial_order
representing the relationship between the two objects. But I wouldn't call that function operator<=>
.
But in any case, is <=>
a zero-overhead abstraction of comparison? No; you can construct cases where it costs significantly more to compute the ordering than it does to detect the specific relation you asked for. But personally if that's the case, there's a good chance that you shouldn't be comparing such types through operators at all.
来源:https://stackoverflow.com/questions/48065905/is-the-three-way-comparison-operator-always-efficient