Skip to the content.

Перегрузка функций


Перегрузка является отличительной особенностью яп С++ в сравнении с Си. Перегрузка выполняется на этапе компиляции.

double square(double d) {return d * d;}
int square(int i) {return i * i;}

int a = square(4); // square(int)
int b = square(3.14); // square(double)
double c = square(5); // square(int)

Стоит отметить что перегрузка не будет работать если изменить только возвращаемый тип:

int getRandomValue();
double getRandomValue();
// Error

Почему не происходит конфликт имён?

int min(int a, int b)
int min(int a, double b)

Потому что во время компиляции имена этих функций поменяются на _Z3minii и _Z3minid, где:

Такая подстановка называется декорирование имени функции.

Обратите внимание, что перегрузка доступна только для С++ и не существует в С, соответственно компилятор С не декорирует имя функции, а оставляет как есть. Для того что бы иметь возможность использовать С функции в С++ стоит указать перед функцией extern “C”, это сообщит компилятору не декорировать имя функции. Очень частая ошибка:

g++ -o test1 test1a.o test1b.o
test1a.o(.text+0x18): In function `main':
: undefined reference to `findmax(int, int)'
collect2: ld returned 1 exit status	

Т.е. линковщик не нашел функцию findmax, обратите внимание, именно findmax без декорации! Это означает что вероятно компилятор C++ декорировал findmax до _Z2findmaxii, а компилятору С была обещана (была объявлена в коде) функция findmax без декорации, которую он и не может найти в итоге.

Наглядный пример

Возьмем .c файл, который будет вызывать функцию, реализованную в .срр:

// main.c

int func(int x, int y, int z);

int main()
{
    func(3, 2, 1);
    return 0;
}
// func.cpp
int func(int x, int y, int z)
{
    return 1;
}

Следовательно, при компиляции (сишным компилятором) на этапе линковки он будет ждать функцию с названием func (без декорации), а .срр (с++ компилятором), который знает о перегрузке, как обычно декорирует название функции: _Z4funciii (сгенерированные названия можно посмотреть в АСМ файле: g++ -S filename). Итого: линковщик не найдёт функцию, произойдет ошибка линковки.

ld.exe: main.o:main.c:(.text+0x1e): undefined reference to `func'
error: ld returned 1 exit status

Но если мы добавим extern "C" к определению функции, то все успешно скомпилируется:

// func.cpp
extern "C"
{
    int func(int x, int y, int z)
    {
        return 1;
    }
}

Потому что extern “C” говорит с++ компилятору не декорировать название функции, оставить в стиле Си.

Стоит обратить внимание, что добавляя extern “C”:

// Так перегружать нельзя, что логично: какую из двух вызывать с Си кода?
extern "C"
{
    int func(int x, int y, int z)
    {
        return 1;
    }

    int func(int x, int y)
    {
        return 1;
    }
}
// А так можно (первая будет участвовать в С коде, а в С++ будет перегрузка):
extern "C"
{
    int func(int x, int y, int z)
    {
        return 1;
    }
}

int func(int x, int y)
{
return 1;
}

extern “C” можно добавлять:

1. К чему-то одному:

// только одна функция
extern "C" void func() {}

2. К блоку:

extern "C"
{
    // много всего
}

Так же существует extern "C++" (включена по умолчанию)

Перегрузка методов


struct Vector2D
{
  Vector2D(double x, double y) : x(x), y(y) {}
  
  // произведение вектора на число
  Vector2D mult(double d) const
  {
      return Vector2D(x * d, y * d);
  }
  
  // произведение вектора на вектор
  double mult(Vector2D const &p) const
  {
      return x * p.x + y * p.y;
  }
  
  double x, y;
};
Vector2D p(1, 2);
Vector2D q = p.mult(10);
double   r = p.mult(q);

Перегрузка при наследовании


struct File
{
  void write(char const * s);
  ...
};

struct Formatted_File : File
{
  void write(int i);
  void write(double d);
  using File::write; // без этой строки методы дочернего класса просто перекроют одноименные родительские методы
  ...
};
Formatted_File f;
f.write(4); // вызовется соотв. функция дочернего класса
f.write("Hello"); // не будет работать без using File::write;