Sdílet prostřednictvím


Přetížení funkcí

Jazyk C++ umožňuje zadat více než jednu funkci se stejným názvem ve stejném oboru. Tyto funkce se nazývají přetížené funkce nebo přetížení. Přetížené funkce umožňují zadat pro funkci různé sémantiky v závislosti na typech a počtu argumentů.

Představte si print například funkci, která přebírá std::string argument. Tato funkce může provádět velmi odlišné úlohy než funkce, která přebírá argument typu double. Přetížení vám brání v použití názvů, jako print_string jsou nebo print_double. V době kompilace kompilátor zvolí, které přetížení se má použít na základě typů a počtu argumentů předaných volajícím. Pokud zavoláte print(42.0), void print(double d) funkce se vyvolá. Pokud zavoláte print("hello world"), pak void print(std::string) přetížení je vyvoláno.

Můžete přetížit členské funkce i bezplatné funkce. Následující tabulka ukazuje, které části deklarace funkce C++ používají k rozlišení skupin funkcí se stejným názvem ve stejném oboru.

Okolnosti přetížení

Element deklarace funkce Používá se k přetížení?
Návratový typ funkce Číslo
Počet argumentů Ano
Typ argumentů Ano
Přítomnost nebo absence tří teček Ano
typedef Použití názvů Číslo
Nespecifikované hranice pole Číslo
const nebo volatile Ano, při použití na celou funkci
Referenční kvalifikátory (& a &&) Ano

Příklad

Následující příklad ukazuje, jak můžete použít přetížení funkcí:

// function_overloading.cpp
// compile with: /EHsc
#include <iostream>
#include <math.h>
#include <string>

// Prototype three print functions.
int print(std::string s);             // Print a string.
int print(double dvalue);            // Print a double.
int print(double dvalue, int prec);  // Print a double with a
                                     //  given precision.
using namespace std;
int main(int argc, char *argv[])
{
    const double d = 893094.2987;
    if (argc < 2)
    {
        // These calls to print invoke print( char *s ).
        print("This program requires one argument.");
        print("The argument specifies the number of");
        print("digits precision for the second number");
        print("printed.");
        exit(0);
    }

    // Invoke print( double dvalue ).
    print(d);

    // Invoke print( double dvalue, int prec ).
    print(d, atoi(argv[1]));
}

// Print a string.
int print(string s)
{
    cout << s << endl;
    return cout.good();
}

// Print a double in default precision.
int print(double dvalue)
{
    cout << dvalue << endl;
    return cout.good();
}

//  Print a double in specified precision.
//  Positive numbers for precision indicate how many digits
//  precision after the decimal point to show. Negative
//  numbers for precision indicate where to round the number
//  to the left of the decimal point.
int print(double dvalue, int prec)
{
    // Use table-lookup for rounding/truncation.
    static const double rgPow10[] = {
        10E-7, 10E-6, 10E-5, 10E-4, 10E-3, 10E-2, 10E-1,
        10E0, 10E1,  10E2,  10E3,  10E4, 10E5,  10E6 };
    const int iPowZero = 6;

    // If precision out of range, just print the number.
    if (prec < -6 || prec > 7)
    {
        return print(dvalue);
    }
    // Scale, truncate, then rescale.
    dvalue = floor(dvalue / rgPow10[iPowZero - prec]) *
        rgPow10[iPowZero - prec];
    cout << dvalue << endl;
    return cout.good();
}

Předchozí kód ukazuje přetížení print funkce v oboru souboru.

Výchozí argument není považován za součást typu funkce. Proto se nepoužívá při výběru přetížených funkcí. Dvě funkce, které se liší pouze v jejich výchozích argumentech, jsou považovány za vícenásobné definice spíše než za přetížené funkce.

Výchozí argumenty nelze zadat pro přetížené operátory.

Porovnávání argumentů

Kompilátor vybere, která přetížená funkce se má vyvolat na základě nejlepší shody mezi deklaracemi funkce v aktuálním oboru na argumenty zadané ve volání funkce. Pokud je nalezena vhodná funkce, volá se tato funkce. "Vhodným" v tomto kontextu se rozumí:

  • Byla nalezena přesná shoda.

  • Provedli jsme triviální převod.

  • Bylo provedeno celočíselné povýšení.

  • Existuje standardní převod na požadovaný typ argumentu.

  • Převod definovaný uživatelem (operátor převodu nebo konstruktor) na požadovaný typ argumentu existuje.

  • Byly nalezeny argumenty reprezentované třemi tečky.

Kompilátor vytvoří sadu kandidátských funkcí pro každý argument. Kandidátské funkce jsou funkce, ve kterých lze skutečný argument v dané pozici převést na typ formálního argumentu.

Pro každý argument se sestaví sada "nejlepších shodných funkcí" a vybraná funkce je průnikem všech sad. Pokud průnik obsahuje více než jednu funkci, přetížení je nejednoznačné a vygeneruje chybu. Funkce, která je nakonec vybrána, je vždy lepší než každá jiná funkce ve skupině pro alespoň jeden argument. Pokud neexistuje žádný jasný vítěz, volání funkce vygeneruje chybu kompilátoru.

Zvažte následující deklarace (funkce jsou označené Variant 1, Variant 2a Variant 3pro identifikaci v následující diskuzi):

Fraction &Add( Fraction &f, long l );       // Variant 1
Fraction &Add( long l, Fraction &f );       // Variant 2
Fraction &Add( Fraction &f, Fraction &f );  // Variant 3

Fraction F1, F2;

Představte si následující příkaz:

F1 = Add( F2, 23 );

Předchozí příkaz sestaví dvě sady:

Set 1: Kandidátské funkce, které mají první argument typu Fraction Set 2: Kandidátské funkce, jejichž druhý argument lze převést na typ int
Varianta 1 Varianta 1 (int lze převést na long použití standardního převodu)
Varianta 3

Funkce v sadě 2 jsou funkce, které mají implicitní převody ze skutečného typu parametru na formální typ parametru. Jedna z těchto funkcí má nejmenší "náklady" pro převod skutečného typu parametru na odpovídající formální typ parametru.

Průsečík těchto dvou sad je Variant 1. Příkladem nejednoznačného volání funkce je:

F1 = Add( 3, 6 );

Předchozí volání funkce sestaví následující sady:

Set 1: Kandidátské funkce, které mají první argument typu int Set 2: Kandidátské funkce, které mají druhý argument typu int
Varianta 2 (int lze převést na long použití standardního převodu) Varianta 1 (int lze převést na long použití standardního převodu)

Protože průnik těchto dvou sad je prázdný, kompilátor vygeneruje chybovou zprávu.

Pro porovnávání argumentů se funkce s n výchozími argumenty považuje za n+1 samostatné funkce, z nichž každá má jiný počet argumentů.

Tři tečky (...) slouží jako zástupný znak; odpovídá jakémukoli skutečnému argumentu. To může vést k mnoha nejednoznačným sadám, pokud nenavrhnete přetížené sady funkcí s extrémní opatrností.

Poznámka

Nejednoznačnost přetížených funkcí nelze určit, dokud nebude zjištěno volání funkce. V tomto okamžiku jsou sady vytvořeny pro každý argument ve volání funkce a můžete určit, zda existuje jednoznačné přetížení. To znamená, že nejednoznačnosti můžou zůstat ve vašem kódu, dokud se nevyvolají konkrétním voláním funkce.

Rozdíly typu argumentů

Přetížené funkce rozlišují mezi typy argumentů přijímajícími různé inicializátory. Proto argument daného typu a reference na tento typ jsou pro účely přetížení považovány za totéž. Považují se za stejné, protože používají stejné inicializátory. Například zápis max( double, double ) je považován za shodný se zápisem max( double &, double & ). Deklarace dvou takových funkcí způsobí chybu.

Z stejného důvodu jsou argumenty funkce typu upraveného typem const nebo volatile nejsou zacházeny jinak než se základním typem pro účely přetížení.

Mechanismus přetížení funkce však může rozlišovat mezi odkazy, které jsou kvalifikované const , a volatile odkazy na základní typ. Umožňuje například následující kód:

// argument_type_differences.cpp
// compile with: /EHsc /W3
// C4521 expected
#include <iostream>

using namespace std;
class Over {
public:
   Over() { cout << "Over default constructor\n"; }
   Over( Over &o ) { cout << "Over&\n"; }
   Over( const Over &co ) { cout << "const Over&\n"; }
   Over( volatile Over &vo ) { cout << "volatile Over&\n"; }
};

int main() {
   Over o1;            // Calls default constructor.
   Over o2( o1 );      // Calls Over( Over& ).
   const Over o3;      // Calls default constructor.
   Over o4( o3 );      // Calls Over( const Over& ).
   volatile Over o5;   // Calls default constructor.
   Over o6( o5 );      // Calls Over( volatile Over& ).
}

Výstup

Over default constructor
Over&
Over default constructor
const Over&
Over default constructor
volatile Over&

Ukazatele na const objekty a volatile ukazatele jsou také považovány za odlišné od ukazatelů na základní typ pro účely přetížení.

Porovnávání argumentů a převody

Když se kompilátor pokusí shodovat se skutečnými argumenty s argumenty v deklaraci funkce, může poskytnout standardní nebo uživatelem definované převody, aby získal správný typ, pokud nelze najít přesnou shodu. Použití převodů podléhá těmto pravidlům:

  • Posloupnosti převodů, které obsahují více než jeden uživatelem definovaný převod, se nepovažují.

  • Posloupnosti převodů, které je možné zkrátit odebráním přechodných převodů, se nepovažují.

Výsledná posloupnost převodů, pokud existuje, se nazývá nejlepší sekvenci odpovídajících výsledků. Existuje několik způsobů, jak převést objekt typu na typ intunsigned long pomocí standardních převodů (popsaných ve standardních převodech):

  • Převeďte z int a long potom long na unsigned long.

  • Převést z int na unsigned long.

I když první sekvence dosáhne požadovaného cíle, není to nejlepší sekvenci, protože existuje kratší sekvence.

Následující tabulka ukazuje skupinu převodů označovaných jako triviální převody. Triviální převody mají omezený vliv na to, která sekvence kompilátor zvolí jako nejlepší shodu. Účinek triviálních převodů je popsán za tabulkou.

Triviální převody

Typ argumentu Převedený typ
type-name type-name&
type-name& type-name
type-name[] type-name*
type-name(argument-list) (*type-name)(argument-list)
type-name const type-name
type-name volatile type-name
type-name* const type-name*
type-name* volatile type-name*

Pořadí pokusů o převody je následující:

  1. Přesná shoda. Přesná shoda mezi typy, se kterými je funkce volána, a typy deklarované v prototypu funkce jsou vždy nejlepší shody. Sekvence triviálních převodů jsou klasifikovány jako přesné shody. Sekvence, které neprovádějí žádný z těchto převodů, se ale považují za lepší než sekvence, které převádějí:

    • Z ukazatele na ukazatel na const (type-name* do const type-name*).

    • Z ukazatele na ukazatel na volatile (type-name* do volatile type-name*).

    • Od odkazu až po odkaz na const (type-name& do const type-name&).

    • Od odkazu až po odkaz na volatile (type-name& do volatile type&).

  2. Porovná s použitím propagačních akcí. Jakákoli sekvence, která není klasifikovaná jako přesná shoda, která obsahuje pouze celočíselné povýšení, převody z float na doublea triviální převody se klasifikují jako shoda pomocí povýšení. I když není tak dobrá shoda jako jakákoli přesná shoda, shoda s propagačními akcemi je lepší než shoda pomocí standardních převodů.

  3. Shoda pomocí standardních převodů Jakákoli sekvence, která není klasifikovaná jako přesná shoda nebo shoda pomocí povýšení, která obsahuje pouze standardní převody a triviální převody, se klasifikuje jako shoda pomocí standardních převodů. V této kategorii se použijí následující pravidla:

    • Převod z ukazatele na odvozenou třídu na ukazatel na přímou nebo nepřímou základní třídu je vhodnější pro převod na void * nebo const void *.

    • Převod z ukazatele na odvozenou třídu na ukazatel na základní třídu vytvoří lepší shodu, která je blíže základní třídě je k přímé základní třídě. Předpokládejme, že hierarchie tříd je znázorněná na následujícím obrázku:

Example class hierarchy showing that class A inherits from B which inherits from C which inherits from D.
Graf zobrazující upřednostňované převody

Převod z typu D* na typ C* je vhodnější pro převod z typu D* na typ B*. Podobně, převod z typu D* na typ B* je vhodnější pro převod z typu D* na typ A*.

Toto pravidlo platí pro převody odkazů. Převod z typu D& na typ C& je vhodnější pro převod z typu D& na typ B&atd.

Toto pravidlo platí pro převody ukazatele na členy. Převod z typu na typ T D::*T C::* je vhodnější pro převod typu T D::* na typ T B::*atd. (kde T je typ člena).

Předchozí pravidlo se vztahuje pouze na danou cestu odvození. Představte si graf zobrazený na následujícím obrázku.

Diagram of multiple inheritance that shows preferred conversions. Class C is the base class of class B and D. Class A inherits from class B
Graf s více dědičností, který zobrazuje upřednostňované převody

Převod z typu C* na typ B* je vhodnější pro převod z typu C* na typ A*. Důvodem je, že jsou na stejné cestě a B* jsou blíž. Převod z typu C* na typ D* ale není vhodnější než převod na typ A*; neexistuje žádná předvolba, protože převody se řídí různými cestami.

  1. Porovná s uživatelsky definovanými převody. Tuto sekvenci nelze klasifikovat jako přesnou shodu, shodu pomocí propagačních akcí nebo shodu pomocí standardních převodů. Aby byla sekvence klasifikována jako shoda s uživatelsky definovanými převody, musí obsahovat pouze uživatelsky definované převody, standardní převody nebo triviální převody. Shoda s uživatelsky definovanými převody se považuje za lepší shodu než shoda se třemi tečkami (...), ale ne tak dobrou shodu jako shoda se standardními převody.

  2. Porovná se třemi tečkami. Jakákoli sekvence, která odpovídá třem tečekm v deklaraci, se klasifikuje jako shoda se třemi tečkami. Považuje se za nejslabší shodu.

Uživatelem definované převody se použijí, pokud neexistuje žádná předdefinovaná povýšení nebo převod. Tyto převody jsou vybrány na základě typu odpovídajícího argumentu. Vezměte v úvahu následující kód:

// argument_matching1.cpp
class UDC
{
public:
   operator int()
   {
      return 0;
   }
   operator long();
};

void Print( int i )
{
};

UDC udc;

int main()
{
   Print( udc );
}

Dostupné uživatelem definované převody pro třídu UDC jsou z typu int a typu long. Kompilátor proto považuje převody pro typ objektu, který se shoduje: UDC. Převod, který int existuje, a je vybraný.

Během procesu shodných argumentů lze u argumentu i výsledku převodu definovaného uživatelem použít standardní převody. Proto funguje následující kód:

void LogToFile( long l );
...
UDC udc;
LogToFile( udc );

V tomto příkladu kompilátor vyvolá uživatelem definovaný převod , operator longpřevést udc na typ long. Pokud nebyl definován žádný uživatelem definovaný převod na typ long , kompilátor nejprve převede typ UDC na typ int pomocí uživatelem definovaného operator int převodu. Pak by se použil standardní převod typu int na typ long tak, aby odpovídal argumentu v deklaraci.

Pokud se k porovnání argumentu vyžadují nějaké převody definované uživatelem, standardní převody se při vyhodnocování nejlepší shody nepoužívají. I když více než jedna kandidátské funkce vyžaduje převod definovaný uživatelem, jsou funkce považovány za stejné. Příklad:

// argument_matching2.cpp
// C2668 expected
class UDC1
{
public:
   UDC1( int );  // User-defined conversion from int.
};

class UDC2
{
public:
   UDC2( long ); // User-defined conversion from long.
};

void Func( UDC1 );
void Func( UDC2 );

int main()
{
   Func( 1 );
}

Obě verze Func vyžadují převod definovaný uživatelem pro převod typu int na argument typu třídy. Možné převody jsou:

  • Převod typu na typ intUDC1 (převod definovaný uživatelem)

  • Převeďte typ na typ intlonga pak převeďte na typ UDC2 (dvoustupňový převod).

I když druhý převod vyžaduje standardní převod i převod definovaný uživatelem, oba převody se stále považují za stejné.

Poznámka

Převody definované uživatelem jsou považovány za převod pomocí konstrukce nebo převodu inicializací. Kompilátor považuje obě metody za stejné, když určí nejlepší shodu.

Porovnávání argumentů this a ukazatel

Členské funkce třídy se zpracovávají odlišně podle toho, jestli jsou deklarovány jako static. static funkce nemají implicitní argument, který poskytuje this ukazatel, takže se považují za jeden menší argument než běžné členské funkce. Jinak se deklarují stejně.

Členské funkce, které nevyžadují static implicitní this ukazatel odpovídající typu objektu, přes který se funkce volá. Nebo pro přetížené operátory vyžadují, aby první argument odpovídal objektu, na který je operátor použit. Další informace o přetížených operátorech naleznete v tématu Přetížené operátory.

Na rozdíl od jiných argumentů v přetížených funkcích kompilátor při pokusu o shodu s argumentem this ukazatele neuvádí žádné dočasné objekty a pokusí se o žádné převody.

Pokud je -> operátor výběru člena použit pro přístup k členské funkci třídy class_name, this argument ukazatele má typ class_name * const. Pokud jsou členy deklarovány jako const nebo volatile, typy jsou const class_name * const a volatile class_name * constv uvedeném pořadí.

Operátor volby členu . funguje přesně stejným způsobem s výjimkou, že je před název objektu přidán implicitní operátor & (adresa). Následující příklad ukazuje tuto funkci:

// Expression encountered in code
obj.name

// How the compiler treats it
(&obj)->name

Levý operand ->* operátorů a .* (ukazatel na člen) se považuje za . operátory a -> (výběr členů) s ohledem na porovnávání argumentů.

Kvalifikátory odkazů na členské funkce

Referenční kvalifikátory umožňují přetížit členskou funkci na základě toho, zda je objekt odkazovaný na this hodnotu rvalue nebo lvalue. Pomocí této funkce se vyhnete zbytečným operacím kopírování ve scénářích, kdy se rozhodnete neposkytnout přístup ukazatele k datům. Předpokládejme například, že třída C inicializuje některá data v jeho konstruktoru a vrátí kopii těchto dat ve členské funkci get_data(). Pokud je objekt typu C rvalue, který se chystá zničit, pak kompilátor zvolí get_data() && přetížení, které se přesune místo kopírování dat.

#include <iostream>
#include <vector>

using namespace std;

class C
{
public:
    C() {/*expensive initialization*/}
    vector<unsigned> get_data() &
    {
        cout << "lvalue\n";
        return _data;
    }
    vector<unsigned> get_data() &&
    {
        cout << "rvalue\n";
        return std::move(_data);
    }

private:
    vector<unsigned> _data;
};

int main()
{
    C c;
    auto v = c.get_data(); // get a copy. prints "lvalue".
    auto v2 = C().get_data(); // get the original. prints "rvalue"
    return 0;
}

Omezení přetížení

Přijatelnou sadu přetížených funkcí řídí několik omezení:

  • Všechny dvě funkce v sadě přetížených funkcí musí mít různé seznamy argumentů.

  • Přetížení funkcí, které mají seznamy argumentů stejných typů na základě samotného návratového typu, je chyba.

    Specifické pro Microsoft

    Můžete přetížit operator new na základě návratového typu, konkrétně na základě modifikátoru modelu paměti zadaného.

    END Microsoft Specific

  • Členské funkce nelze přetížit pouze proto, že jedna je static a druhá není static.

  • typedef deklarace nedefinují nové typy; představují synonyma pro existující typy. Nemají vliv na mechanismus přetížení. Vezměte v úvahu následující kód:

    typedef char * PSTR;
    
    void Print( char *szToPrint );
    void Print( PSTR szToPrint );
    

    Předchozí dvě funkce mají identické seznamy argumentů. PSTR je synonymem pro typ char *. V oboru člena tento kód vygeneruje chybu.

  • Výčtové typy jsou odlišné typy a lze je použít k rozlišení mezi přetíženými funkcemi.

  • Typy "pole" a "ukazatel na" jsou považovány za identické pro účely rozlišování mezi přetíženými funkcemi, ale pouze pro jednorozměrná pole. Tyto přetížené funkce kolidují a generují chybovou zprávu:

    void Print( char *szToPrint );
    void Print( char szToPrint[] );
    

    U polí vyšších dimenzí jsou druhé a pozdější dimenze považovány za součást typu. Používají se k rozlišení mezi přetíženými funkcemi:

    void Print( char szToPrint[] );
    void Print( char szToPrint[][7] );
    void Print( char szToPrint[][9][42] );
    

Přetížení, přepsání a skrytí

Jakékoli dvě deklarace funkce se stejným názvem ve stejném oboru mohou odkazovat na stejnou funkci nebo na dvě samostatné přetížené funkce. Pokud seznam deklarací argumentu obsahuje argumenty ekvivalentních typů (jak je popsáno v předchozí části), deklarace funkce odkazují na stejnou funkci. V opačném případě odkazují na dvě různé funkce, které jsou vybrány pomocí přetížení.

Obor třídy je přísně sledován. Funkce deklarovaná v základní třídě není ve stejném oboru jako funkce deklarovaná v odvozené třídě. Pokud je funkce v odvozené třídě deklarována se stejným názvem jako virtual funkce v základní třídě, funkce odvozené třídy přepíše funkci základní třídy. Další informace najdete v tématu Virtuální funkce.

Pokud není funkce základní třídy deklarována jako virtual, znamená to, že odvozená funkce třídy je skryta. Přepsání i skrytí se liší od přetížení.

Rozsah bloku je přísně sledován. Funkce deklarovaná v oboru souboru není ve stejném oboru jako funkce deklarovaná místně. Pokud má funkce deklarovaná místně stejný název jako funkce deklarovaná v rozsahu souboru, funkce deklarovaná místně skryje funkci s rozsahem souboru místo toho, aby způsobila přetížení. Příklad:

// declaration_matching1.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
void func( int i )
{
    cout << "Called file-scoped func : " << i << endl;
}

void func( char *sz )
{
    cout << "Called locally declared func : " << sz << endl;
}

int main()
{
    // Declare func local to main.
    extern void func( char *sz );

    func( 3 );   // C2664 Error. func( int ) is hidden.
    func( "s" );
}

Předchozí kód zobrazí dvě definice z funkce func. Definice, která přebírá argument typu char * , je místní vzhledem k mainextern příkazu. Proto je definice, která přebírá argument typu int , skrytá a první volání func je omylem.

U přetížených členských funkcí lze různým verzím funkce předat různá přístupová oprávnění. Stále se považují za obor nadřazené třídy, a proto jsou přetížené funkce. Uvažujme následující kód, ve kterém je členská funkce Deposit přetížena. Jedna verze je veřejná, druhá soukromá.

Záměrem tohoto příkladu je poskytnout třídu Account, ve které je požadováno správné heslo pro provedení vkladů. To se provádí pomocí přetížení.

Volání DepositAccount::Deposit volání funkce privátního člena. Toto volání je správné, protože Account::Deposit je členovou funkcí a má přístup k soukromým členům třídy.

// declaration_matching2.cpp
class Account
{
public:
   Account()
   {
   }
   double Deposit( double dAmount, char *szPassword );

private:
   double Deposit( double dAmount )
   {
      return 0.0;
   }
   int Validate( char *szPassword )
   {
      return 0;
   }

};

int main()
{
    // Allocate a new object of type Account.
    Account *pAcct = new Account;

    // Deposit $57.22. Error: calls a private function.
    // pAcct->Deposit( 57.22 );

    // Deposit $57.22 and supply a password. OK: calls a
    //  public function.
    pAcct->Deposit( 52.77, "pswd" );
}

double Account::Deposit( double dAmount, char *szPassword )
{
   if ( Validate( szPassword ) )
      return Deposit( dAmount );
   else
      return 0.0;
}

Viz také

Funkce (C++)