Надеюсь, многие знают, что в C++20 появилась замечательная штука operator<=>. Стоит его определить, как компилятор на его основе выводит другие операторы сравнения, вроде operator< или operator>=.
Причем, что особенно хорошо, можно попросить компилятор сгенерировать operator<=> для нас автоматически:
|
struct example { ... // Какие-то поля. auto operator<=>(const example &) const = default; }; |
И все. Остальное за нас сделает компилятор.
Однако, думаю, не все знают, что если нам не подходит штатная реализация и мы пишем operator<=> сами, то компилятор потребует, чтобы мы еще и определили свой собственный operator==.
Например, допустим, что у нас в example три поля, а в сравнении должно участвовать только два из них:
|
struct example { int m_a; int m_b; int m_c; auto operator<=>(const example & o) const noexcept { return std::tie(m_a, m_c) <=> std::tie(o.m_a, o.m_c); } }; |
В этом случае мы внезапно© обнаружим, что у нас нет operator==.
Таков путь: если мы определили свой operator<=>, то компилятор не будет автоматически выводить равенство и неравенство, а потребует, чтобы мы предоставили operator==.
Может быть вам будет интересно почему?
Если да, то лучше всего прочитать раздел с мотивацией из предложения P1185 и вот этот ответ на Stackoverflow. В двух же словах: реализация сравнения на строгое равенство на базе operator<=> может быть весьма неэффективна.
Например, представим, что мы сравниваем две строки: s1="AA" и s2="AAA".
При лексикографическом сравнении s1 меньше, чем s2: общая подстрока в s1 и s2 совпадает, но в s1 символов меньше.
Когда мы для наших строк s1 и s2 вызываем operator<=>, то этот оператор пытается выяснить как именно соотносятся s1 и s2: меньше ли s1, чем s2, или же s1 больше, чем s2, или же они равны.
Теперь допустим, что в s1 находится миллион символов "A", а в s2 -- миллион и один символ "A". При вызове operator<=> придется сравнить миллион символов в общей подстроке прежде чем мы сможем понять, что s1 все-таки меньше, т.к. у нее символов меньше.
При этом мы не можем прервать сравнение раньше. Допустим, что в s1 будет девятьсот девяносто девять тысяч и девятьсот девяносто девять символов "A", а за ними единственный символ "B", тогда как в s2 все миллион + один символ -- это "A". В таком случае строка s1 окажется больше, не смотря на то, что символов в ней меньше.
Однако, если нам не нужно выяснять общее соотношение между s1 и s2, а достачно просто понять равны ли они или нет, то мы можем начать со сравения длин и лишь затем переходить к посимвольному сравнению.
Когда компилятор по нашей просьбе сам генерирует operator<=>, то он эти фокусы учитывает (под катом небольшой тестовый бенчмарк, который позволяет убедится, что сравнение двух неравных векторов требует минимального времени). Однако, если operator<=> предоставляет пользователь, то у компилятора нет уверенности в том, что пользователь написал свой operator<=> с учетом возможностей оптимизации операции "строгое равенство". И просит нас взять ответственность за operator== на себя.
Вот несложный код, который позволяет убедиться, что программа не делает сравнения каждого из миллиона элементов вектора внутри структуры owner:
|
#include <chrono> #include <iostream> #include <string> #include <vector> struct owner { std::vector< std::string > m_strings; auto operator<=>(const owner &) const = default; }; int main() { owner first; for( std::size_t i = 0; i != 1'000'000; ++i ) first.m_strings.emplace_back( "just a simple string" ); owner second{ first }; second.m_strings.pop_back(); const auto started_at = std::chrono::high_resolution_clock::now(); int valid_results{}; for( int i = 0; i < 100'000; ++i ) if( first != second ) ++valid_results; const auto finished_at = std::chrono::high_resolution_clock::now(); std::cout << "results: " << valid_results << ", time: " << std::chrono::duration_cast< std::chrono::microseconds >( finished_at - started_at ).count() << " us" << std::endl; } |
Комментариев нет:
Отправить комментарий