Hvordan definere sammenligningsoperatører som standard i C++

 C Programming >> C C# Program >  >> C++
Hvordan definere sammenligningsoperatører som standard i C++

Å implementere sammenligningsoperatorer i C++ er lettere sagt enn gjort.

Faktisk, for de fleste typer, hvis vi kunne snakke med kompilatoren, ville vi si noe sånt som:"for å bestille dem, bruk en leksikografisk rekkefølge på medlemmene deres".

Men når det gjelder å skrive den tilsvarende koden, blir ting mer komplisert.

Imidlertid en klassisk teknikk som bruker std::tuple gjør koden mye mer kortfattet for sammenligningsoperatører, og den bør brukes som standard. (Minst før C++20, ettersom C++20 gjorde sammenligningsoperatorer enda enklere å skrive).

La oss se teknikken som involverer std::tuple og deretter hvordan sammenligningsoperatørens situasjon utvikler seg med C++20.

En naiv implementering

Før C++20 og uten å bruke std::tuple , koden for operator< kan være komplisert.

Tenk på følgende klasse for å illustrere:

struct MyType
{
    int member1;
    std::string member2;
    std::vector<double> member3;
    int member4;
    double member5;
};

Skriver operator< for hånd kan se slik ut:

bool operator<(MyType const& lhs, MyType const& rhs)
{
    if (lhs.member1 < rhs.member1) return true;
    if (rhs.member1 < lhs.member1) return false;

    if (lhs.member2 < rhs.member2) return true;
    if (rhs.member2 < lhs.member2) return false;

    if (lhs.member3 < rhs.member3) return true;
    if (rhs.member3 < lhs.member3) return false;

    if (lhs.member4 < rhs.member4) return true;
    if (rhs.member4 < lhs.member4) return false;

    return lhs.member5 < rhs.member5;
}

Denne koden er mer komplisert enn den burde. Faktisk er intensjonen til programmereren å "gjøre det naturlige", som betyr for operator< en leksikografisk sammenligning. Men denne koden sier det ikke eksplisitt.

I stedet inviterer den leseren til å inspisere den, kjøre den i hodet, formulere hypotesen om at det er en leksikografisk sammenligning, og kjøre den igjen i hodet for å være sikker. Ikke egentlig uttrykksfull kode.

Dessuten er denne koden farlig. En skrivefeil kan lett skli inn og forårsake en feil. Og i praksis skjer dette! Jeg har fikset slike feil flere ganger. En av dem tok meg litt tid å diagnostisere, siden dens effekt var å lage std::sort algoritmekrasj, bare på visse plattformer. Fint.

Selv før C++20 er det en mer uttrykksfull og tryggere måte å skrive sammenligningsoperatorer på.

Sammenlign typen som en std::tuple

Vi ønsker leksikografisk sammenligning på medlemmene i klassen. En måte å oppnå dette på er å gjenbruke noe eksisterende kode i standardbiblioteket som allerede implementerer leksikografisk sammenligning:sammenligningen av std::tuples .

Faktisk, std::tuple har sammenligningsoperatorer, og de implementerer leksikografiske sammenligninger. Vi kan derfor sette alle medlemmene av typen inn i en tuppel, og bruke sammenligningsoperatorene til std::tuple .

Men vi vil ikke lage kopier av hvert medlem av typen til en tuppel hver gang vi sammenligner to objekter. I stedet kan vi lage en tuppel med referanser til medlemmene og sammenligne dem, noe som unngår kopier og beholder fordelen med å gjenbruke koden std::tuple .

For å lage en std::tuple av referanser, kan vi bruke std::tie . Her er den resulterende koden:

bool operator<(MyType const& lhs, MyType const& rhs)
{
    return std::tie(lhs.member1, lhs.member2, lhs.member3, lhs.member4, lhs.member5)
         < std::tie(rhs.member1, rhs.member2, rhs.member3, rhs.member4, rhs.member5);
}

Denne koden er mer kortfattet, sikrere og mer uttrykksfull enn den forrige implementeringen:den sier at medlemmene sammenlignes som en tuppel sammenligner elementene, som betyr i leksikografisk rekkefølge.

Når det er sagt, må man vite std::tie for å forstå denne koden. Men std::tie er en vanlig komponent i standardbiblioteket, og er en del av det vanlige vokabularet til C++-utviklere.

For en mer avansert teknikk som implementerer alle Sammenlign operatører med denne teknikken med lite tilleggskode, sjekk ut Hvordan emulere romskipsoperatøren før C++20 med CRTP.

I C++20

I C++20, implementeringen av operator< blir enda mer kortfattet, trygg og uttrykksfull:

struct MyType
{
    int member1;
    std::string member2;
    std::vector<double> member3;
    int member4;
    double member5;

    friend bool operator<(MyType const& lhs, MyType const& rhs) = default;
};

Med = default , sier vi bare til kompileringen:"gjør det rette". Dette er imidlertid ikke hvordan vi skal definere operatører som standard i C++20. En bedre måte er å bruke romskipoperatøren :

struct MyType
{
    int member1;
    std::string member2;
    std::vector<double> member3;
    int member4;
    double member5;

    friend bool operator<=>(MyType const& lhs, MyType const& rhs) = default;
};

På denne måten får vi ikke bare operator< , men vi får også operator== , operator!= , operator> , operator<= , operator>= og operator<=> med deres implementeringer som standard.

Hver versjon av C++ har sine mange funksjoner for å gjøre koden vår uttrykksfull. Men før de nyere versjonene kommer, kan vi fortsatt prøve å skrive enkel kode med funksjonene vi har til rådighet.

Du vil også like

  • Hvordan emulere romskipsoperatøren før C++20 med CRTP
  • Kompilatorgenererte funksjoner, regel med tre og regel om fem
  • Nullregelen i C++
  • De overraskende begrensningene til C++-områder utover trivielle tilfeller
  • En kortfattet implementering av Fizzbuzz med std::valgfritt