Sobrecarga de função

O C++ permite a você especificar mais de uma função do mesmo nome no mesmo escopo. Essas funções são chamadas de funções sobrecarregadas ou sobrecargas. As funções sobrecarregadas permitem a você fornecer semânticas diferentes para uma função, dependendo dos tipos e do número de argumentos dela.

Por exemplo, considere uma função print que usa um argumento std::string. Essa função pode executar tarefas muito diferentes de uma função que usa um argumento do tipo double. A sobrecarga evita que você tenha que usar nomes como print_string ou print_double. No tempo de compilação, o compilador escolhe qual sobrecarga usar com base nos tipos e no número de argumentos passados pelo chamador. Se você chamar print(42.0), a função void print(double d) será invocada. Se você chamar print("hello world"), a sobrecarga void print(std::string) será invocada.

Você pode sobrecarregar ambas as funções membro e funções gratuitas. A tabela a seguir mostra quais partes de uma declaração de função C++ usa para diferenciar entre grupos de funções com o mesmo nome no mesmo escopo.

Considerações de sobrecarga

Elemento de declaração de função Usado para sobrecarga?
Tipo de retorno de função Não
Número de argumentos Sim
Tipo de argumentos Sim
Presença ou ausência de reticências Sim
Uso de nomes typedef Não
Limites de matriz não especificados Não
const ou volatile Sim, quando aplicado a toda a função
Qualificadores de referência (& e &&) Sim

Exemplo

O exemplo a seguir ilustra como você pode usar sobrecargas de função:

// 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();
}

O código anterior mostra sobrecargas da função print no escopo do arquivo.

O argumento padrão não é considerado parte do tipo de função. Consequentemente, não é usado na seleção de funções sobrecarregadas. Duas funções que diferem apenas nos argumentos padrão são consideradas várias definições em vez de funções sobrecarregadas.

Não é possível fornecer argumentos padrão para operadores sobrecarregados.

Correspondência de argumentos

O compilador seleciona qual função sobrecarregada invocar com base na melhor correspondência entre as declarações de função no escopo atual para os argumentos fornecidos na chamada de função. Se uma função apropriada for localizada, essa função é chamada. “Apropriada” neste contexto significa uma das seguintes opções:

  • Uma correspondência exata foi encontrada.

  • Uma conversão trivial foi executada.

  • Uma promoção integral foi executada.

  • Uma conversão padrão para o tipo de argumento desejado existe.

  • Uma conversão definida pelo usuário (um operador ou um construtor de conversão) para o tipo de argumento desejado existe.

  • Argumentos representados por reticências foram encontrados.

O compilador cria um conjunto de funções candidatas para cada argumento. As funções candidatas são funções em que o argumento real nessa posição pode ser convertido no tipo do argumento formal.

Um conjunto de “melhores funções correspondentes” é criado para cada argumento, e a função selecionada é a interseção de todos os conjuntos. Se a interseção contiver mais de uma função, a sobrecarga é ambígua e gera um erro. A função que é selecionada sempre é uma correspondência melhor de que todas as outras funções no grupo para no mínimo um argumento. Se não houver um vencedor claro, a chamada de função gerará um erro do compilador.

Observe as seguintes declarações (as funções são marcadas Variant 1, Variant 2 e Variant 3, para identificação na discussão a seguir):

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;

Considere o seguinte instrução:

F1 = Add( F2, 23 );

A instrução anterior compila dois conjuntos:

Conjunto 1: funções candidatas que têm o primeiro argumento do tipo Fraction Conjunto 2: funções candidatas cujo segundo argumento pode ser convertido no tipo int int
Variant 1 Variante 1 (int pode ser convertida em long usando uma conversão padrão)
Variante 3

As funções no Conjunto 2 têm conversões implícitas do tipo de parâmetro real para o tipo de parâmetro formal. Uma dessas funções tem o menor "custo" para converter o tipo de parâmetro real no respectivo tipo de parâmetro formal correspondente.

A interseção desses dois conjuntos é a Variant 1. Um exemplo de uma chamada de função ambígua é:

F1 = Add( 3, 6 );

A chamada de função anterior compila os seguintes conjuntos:

Conjunto 1: funções candidatas que têm o primeiro argumento do tipo int Conjunto 2: funções candidatas que têm o segundo argumento do tipo int
Variante 2 (int pode ser convertida em long usando uma conversão padrão) Variante 1 (int pode ser convertida em long usando uma conversão padrão)

Como a interseção desses dois conjuntos está vazia, o compilador gera uma mensagem de erro.

Para a correspondência do argumento, uma função com n argumentos padrão é tratada como n+1 funções separadas, cada uma com um número diferente de argumentos.

As reticências (...) atuam como um curinga; elas correspondem a qualquer argumento real. Isso poderá resultar em muitos conjuntos ambíguos, se você não criar seus conjuntos de função sobrecarregada com extremo cuidado.

Observação

A ambiguidade das funções sobrecarregadas não pode ser determinada até que uma chamada de função seja encontrada. Nesse ponto, os conjuntos são compilados para cada argumento na chamada de função, e você pode determinar se há uma sobrecarga inequívoca. Isso significa que as ambiguidades podem permanecer em seu código até que sejam evocadas por uma chamada de função específica.

Diferenças de tipo de argumento

As funções sobrecarregadas diferenciam-se entre os tipos de argumento que têm inicializadores diferentes. Portanto, um argumento de um determinado tipo e uma referência a esse tipo são considerados iguais para fins de sobrecarga. Eles são considerados iguais porque têm os mesmos inicializadores. Por exemplo, max( double, double ) é considerado o mesmo que max( double &, double & ). Declarar essas duas funções causa um erro.

Pela mesma razão, os argumentos de função de um tipo modificado por const ou volatile não são tratados de modo diferente do tipo de base para fins de sobrecarga.

Entretanto, o mecanismo de sobrecarga de função pode distinguir entre as referências que estão qualificadas por const e volatile e referências ao tipo base. Isso possibilita escrever código como o seguinte:

// 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& ).
}

Saída

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

Os ponteiros para objetos const e volatile também são considerados diferentes dos ponteiros para o tipo base para fins de sobrecarga.

Correspondência de argumentos e conversões

Quando o compilador tenta corresponder argumentos reais com os argumentos em declarações de função, ele pode fornecer conversões padrão ou definidas pelo usuário para obter o tipo correto se nenhuma correspondência exata for encontrada. A aplicação de conversões está sujeita a estas regras:

  • As sequências de conversões que contêm mais de uma conversão definida pelo usuário não são consideradas.

  • As sequências de conversões que podem ser encurtadas removendo as conversões intermediárias não são consideradas.

A sequência de conversões resultante, se houver, será considerada a melhor sequência de correspondência. Há várias maneiras de converter um objeto de tipo int em tipo unsigned long usando as conversões padrão (descritas em Conversões padrão):

  • Converter int em long e depois long em unsigned long.

  • Converter int em unsigned long.

Embora a primeira sequência atinja a meta desejada, ela não é a melhor sequência correspondente, pois existe uma sequência mais curta.

A tabela a seguir mostra um grupo de conversões chamadas conversões triviais. As conversões triviais têm um efeito limitado sobre qual sequência o compilador escolhe como a melhor correspondência. O efeito das conversões triviais é descrito após a tabela.

Conversões triviais

Tipo de argumento Tipo convertido
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*

A sequência em que as conversões são executadas é a seguinte:

  1. Correspondência exata. Uma correspondência exata entre os tipos com que a função é chamada e os tipos declarados no protótipo da função sempre é a melhor correspondência. As sequências de conversões triviais são classificadas como correspondências exatas. No entanto, as sequências que não fazem qualquer uma dessas conversões são consideradas melhor do que as sequências que convertem:

    • De ponteiro, em ponteiro em const (type-name* em const type-name*).

    • De ponteiro, em ponteiro em volatile (type-name* em volatile type-name*).

    • De referência, em referência em const (type-name& em const type-name&).

    • De referência, em referência em volatile (type-name& em volatile type&).

  2. Correspondência usando promoções. Qualquer sequência não classificada como correspondência exata que contém somente promoções integrais, conversões de float em double e as conversões triviais é classificada como correspondência usando promoções. Embora não seja tão boa quanto a correspondência exata, a correspondência usando promoções é melhor do que a correspondência usando conversões padrão.

  3. Correspondência usando conversões padrão. Qualquer sequência não classificada como correspondência exata ou correspondência usando promoções que contém somente conversões padrão e conversões triviais é classificada como correspondência usando conversões padrão. Nessa categoria, as seguintes regras são aplicadas:

    • A conversão de um ponteiro em uma classe derivada, em um ponteiro em uma classe base direta ou indireta é preferível à conversão de void * em const void *.

    • A conversão de um ponteiro em uma classe derivada, em um ponteiro em uma classe base gera uma correspondência melhor quanto mais próxima a classe base estiver de uma classe base direta. Suponha que a hierarquia de classe seja como mostrado na figura a seguir:

Example class hierarchy showing that class A inherits from B which inherits from C which inherits from D.
Grafo mostrando as conversões preferenciais.

A conversão do tipo D* no tipo C* é preferível à conversão do tipo D* no tipo B*. Da mesma forma, a conversão do tipo D* no tipo B* é preferível à conversão do tipo D* no tipo A*.

Essa mesma regra se aplica para referenciar conversões. A conversão do tipo D& no tipo C& é preferível à conversão do tipo D& no tipo B&.

Essa mesma regra se aplica às conversões de ponteiro em membro. A conversão do tipo T D::* no tipo T C::* é preferível à conversão do tipo T D::* no tipo T B::*, e assim por diante (onde T é o tipo do membro).

A regra anterior só se aplica ao longo de um caminho específico de derivação. Considere o gráfico mostrado na figura a seguir.

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
Grafo de herança múltipla que mostra as conversões preferenciais.

A conversão do tipo C* no tipo B* é preferível à conversão do tipo C* no tipo A*. A razão é que eles estão no mesmo caminho, e B* é mais próximo. No entanto, a conversão do tipo C* em D* não é preferível à conversão no tipo A*; não há preferência porque as conversões seguem caminhos diferentes.

  1. Correspondência com conversões definidas pelo usuário. Essa sequência não pode ser classificada como correspondência exata, correspondência usando promoções ou correspondência usando conversões padrão. Para ser classificada como uma correspondência com conversões definidas pelo usuário, a sequência precisa conter apenas conversões definidas pelo usuário, conversões padrão ou conversões triviais. Uma correspondência com conversões definidas pelo usuário é considerada uma correspondência melhor do que uma correspondência com um sinal de reticências (...), mas não tão boa quanto uma correspondência com conversões padrão.

  2. Correspondência com um sinal de reticências. Qualquer sequência que corresponda a reticências na declaração é classificada como correspondência com um sinal de reticências. Essa é considerada a correspondência mais fraca.

As conversões definidas pelo usuário são aplicadas quando não há promoção ou conversão. Essas conversões são selecionadas com base no tipo do argumento que está sendo correspondido. Considere o seguinte código:

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

void Print( int i )
{
};

UDC udc;

int main()
{
   Print( udc );
}

As conversões definidas pelo usuário disponíveis para a classe UDC são do tipo int e do tipo long. Portanto, o compilador considera conversões para o tipo do objeto que está sendo correspondido: UDC. Uma conversão em int existe e é selecionada.

Durante o processo de correspondência de argumentos, as conversões padrão podem ser aplicadas ao argumento e ao resultado de uma conversão definida pelo usuário. Portanto, o código a seguir funciona:

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

Neste exemplo, o compilador invoca uma conversão definida pelo usuário, operator long, para converter udc no tipo long. Se nenhuma conversão definida pelo usuário para o tipo long fosse definida, o compilador converteria primeiro o tipo UDC no tipo int usando a conversão operator int definida pelo usuário. Em seguida, ele aplicaria a conversão padrão do tipo int no tipo long para corresponder ao argumento na declaração.

Se uma conversão definida pelo usuário for necessária para corresponder a um argumento, as conversões padrão não serão usadas na avaliação da melhor correspondência. Mesmo se mais de uma função candidata exigir uma conversão definida pelo usuário, as funções serão consideradas iguais. Por exemplo:

// 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 );
}

Ambas as versões de Func exigem uma conversão definida pelo usuário para converter o tipo int no argumento do tipo de classe. As conversões possíveis são:

  • Converter do tipo int no tipo UDC1 (uma conversão definida pelo usuário).

  • Converter do tipo int no tipo long; depois, converter no tipo UDC2 (uma conversão de duas etapas).

Mesmo que a segunda exija uma conversão padrão e também a conversão definida pelo usuário, as duas conversões ainda são consideradas iguais.

Observação

As conversões definidas pelo usuário são consideradas conversão por construção ou conversão por inicialização. O compilador considera ambos os métodos iguais quando determina a melhor correspondência.

Correspondência de argumento e o ponteiro this

As funções membro de classe são tratadas de modo diferente, dependendo se são declaradas como static. As funções static não têm um argumento implícito que forneça o ponteiro this, portanto, elas são consideradas como tendo um argumento a menos do que as funções membro comuns. Exceto por esse detalhe, elas são declarados de maneira idêntica às demais.

Funções membro não static exigem o ponteiro this implícito para corresponder ao tipo de objeto pelo qual a função está sendo chamada. Ou, para operadores sobrecarregados, elas exigem que o primeiro argumento corresponda ao objeto ao qual o operador é aplicado. Para obter mais informações sobre operadores sobrecarregados, confira Operadores sobrecarregados.

Ao contrário de outros argumentos em funções sobrecarregadas, nenhum objeto temporário é introduzido e nenhuma conversão é tentada ao tentar corresponder ao argumento do ponteiro this.

Quando o operador de seleção de membro -> é usado para acessar uma função membro de classe class_name, o argumento de ponteiro this tem um tipo class_name * const. Se os membros forem declarados como const ou volatile, os tipos serão const class_name * const e volatile class_name * const, respectivamente.

O operador de seleção de membro . funciona exatamente da mesma maneira, exceto que um operador & (address-of) implícito tem um prefixo no nome do objeto. O exemplo a seguir mostra como isso funciona:

// Expression encountered in code
obj.name

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

O operando à esquerda dos operadores ->* e .* (ponteiro para membro) é tratado da mesma forma que os operadores . e -> (seleção de membro) em relação à correspondência do argumento.

Qualificadores de referência em funções membro

Os qualificadores de referência possibilitam sobrecarregar uma função membro com base em se o objeto apontado por this é um rvalue ou um lvalue. Use esse recurso para evitar operações de cópia desnecessárias em cenários em que você opta por não fornecer acesso de ponteiro aos dados. Por exemplo, suponha que a classe C inicialize alguns dados no próprio construtor e retorne uma cópia desses dados na função membro get_data(). Se um objeto de tipo C for um rvalue prestes a ser destruído, o compilador escolherá a sobrecarga get_data() &&, que move os dados em vez de copiá-los.

#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;
}

Restrições de sobrecarga

Várias restrições regem um conjunto de funções sobrecarregadas aceitável:

  • Quaisquer duas funções em um conjunto de funções sobrecarregadas devem ter listas de argumentos diferentes.

  • Sobrecarregar funções com listas de argumentos dos mesmos tipos, com base apenas no tipo de retorno, é um erro.

    Seção específica da Microsoft

    Você pode sobrecarregar operator new com base no tipo de retorno, especificamente, com base no modificador de modelo de memória especificado.

    Fim da seção específica da Microsoft

  • As funções membro não podem ser sobrecarregadas apenas porque uma é static e a outra não static.

  • Declarações typedef não definem novos tipos. Elas introduzem sinônimos para tipos existentes. Elas não afetam o mecanismo de sobrecarga. Considere o seguinte código:

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

    As duas funções anteriores têm listas de argumento idênticas. PSTR é um sinônimo do tipo char *. No escopo do membro, esse código gera um erro.

  • Os tipos enumerados são tipos distintos e podem ser usados para distinguir as funções sobrecarregadas.

  • Os tipos "matriz de" e "ponteiro para" são considerados idênticos para fins de distinção das funções sobrecarregadas, mas somente para matriz unidimensionais. Estas funções sobrecarregadas entram em conflito e geram uma mensagem de erro:

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

    Para matrizes com mais de uma dimensão, a segunda e todas as dimensões seguintes são consideradas parte do tipo. Portanto, elas são usadas para distinguir entre funções sobrecarregadas:

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

Sobrecarga, substituição e ocultação

Quaisquer duas declarações de função do mesmo nome no mesmo escopo podem fazer referência à mesma função, ou duas funções discretas sobrecarregadas. Se as listas de argumento de declarações contiverem argumentos de tipos equivalentes (como descrito na seção anterior), as declarações de função se referem à mesma função. Se não, fazem referência a duas funções diferentes que são selecionadas usando a sobrecarga.

O escopo da classe é estritamente observado. Uma função declarada em uma classe base não está no mesmo escopo que uma função declarada em uma classe derivada. Se uma função em uma classe derivada for declarada com o mesmo nome de uma função virtual na classe base, a função de classe derivada substitui a função da classe base. Para mais informações, confira Funções virtuais.

Se a função de classe base não for declarada como virtual, a função de classe derivada a oculta. A substituição e a ocultação são distintas da sobrecarga.

O escopo do bloco é observado estritamente. Uma função declarada em um escopo de arquivo não está no mesmo escopo que uma função declarada localmente. Se uma função declarada localmente tiver o mesmo nome de uma função declarada no escopo de arquivo, a função declarada localmente oculta a função do escopo de arquivo ao invés de causar a sobrecarga. Por exemplo:

// 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" );
}

O código anterior mostra duas definições da função func. A definição que usa um argumento do tipo char * é local para main devido à instrução extern. Consequentemente, a definição que usa um argumento do tipo int é ocultada, e a primeira chamada para func está em erro.

Para funções de membro sobrecarregadas, as versões diferentes da função podem receber privilégios de acesso diferentes. Elas são consideradas como ainda no escopo da classe delimitadora e, portanto, são funções sobrecarregadas. Considere o seguinte código, no qual a função de membro Deposit é sobrecarregada; uma versão é pública, a outro, privada.

A finalidade deste exemplo é fornecer uma classe Account em que uma senha correta é necessária para executar depósitos. Isso é feito usando sobrecarga.

A chamada para Deposit em Account::Deposit chama a função membro privada. Essa chamada está correta pois Account::Deposit é uma função membro e tem acesso a membros privados da classe.

// 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;
}

Confira também

Funções (C++)