Каково предпочтение разрешения имени функции / метода / шаблона в C++?

Как компилятор C++ решает, какую функцию / метод вызывать, если существует несколько возможностей? В моем конкретном случае у меня есть стандартная свободная функция времени выполнения C++, и у меня также есть шаблонный бесплатный вариант, например:

// The definitions of the C++ Run Time Library (from memory.h)
extern malloc(size_t s);
extern void free(void *p);

// Our own memory management functions
extern void *OurMalloc(size_t s);
extern void OurFree(void *p);

// Own variants to overrule malloc and free (instead of using #define)
template<typename T>
void *malloc(T t)
{
return OurMalloc(t);
}

template<typename T>
void free(T *t)
{
OurFree(t);
}

Я проверил это, используя следующий код:

void main(void)
{
void *p = malloc(10);
free(p);
}

Если я компилирую и запускаю это, кажется, что вызов malloc правильно заменен шаблонным вариантом. Все идет нормально.

Однако вызов free не заменяется шаблонным вариантом, и стандартная функция C++ по-прежнему вызывается.

Какие правила использует компилятор C++, чтобы решить, какой вариант отдавать приоритет? Это связано с правилами поиска Кенига?

Примечание: я попробовал эту альтернативу, потому что использование #define не решает проблему (см. Вопрос Как использовать макрос C (#define) для изменения вызовов, но не прототипов).

4 ответа

Решение

Разрешение перегрузки в целом довольно сложно.

В вашем случае это довольно просто: шаблон функции не учитывается, если есть точное совпадение. Бесплатно это так (стандартное освобождение занимает пустоту *), для malloc это не так (стандартное malloc принимает size_t, вы передаете int, а size_t не может быть typedef для int - size_t не подписано). Если вы звоните бесплатно с типом, отличным от void*, он должен создать экземпляр вашего шаблона.

Бег:

#include <iostream>

void* ml(size_t s)
{
    std::cout << "ml(size_t)\n";
}

void fr(void *p)
{
    std::cout << "fr(void*)\n";
}

template<typename T>
void* ml(T t)
{
    std::cout << "ml<" << typeid(T).name() << ">(T)\n";
}

template<typename T>
void fr(T *t)
{
    std::cout << "fr<" << typeid(T).name() << ">(T*)\n";
}

int main()
{
    void* p1 = ml((size_t)10);
    fr(p1);
    int* p2 = (int*)ml(10);
    fr(p2);
    return 0;
}

я получил

ml(size_t)
fr(void*)
ml<i>(T)
fr<i>(T*)

и я это то, что возвращается typeid(int).name()

Для вашего конкретного вопроса о malloc а также freeпроблема в том, что в вашем звонке malloc:

void *p = malloc(10);

параметр 10 набирается как intв то время как подпись для среды выполнения malloc() призывает к unsigned аргумент. Так как нет точного соответствия, компилятор предпочитает шаблонный malloc где это может создать точное совпадение.

Когда вы звоните:

free(p);

тип p является void* который точно соответствует подписи среды выполнения для free() так что компилятор не утруждает себя использованием шаблонного free,

Невозможно "заменить" стандарт malloc используя эту технику. Другие ответы уже объясняли это, потому что вы используете значение со знаком в качестве аргумента в malloc call, ваша шаблонная версия "выигрывает" у стандартной, потому что стандартная версия ожидает неподписанный аргумент.

Чтобы лучше проиллюстрировать это, я просто хотел добавить, что если вы поставите либо unsigned int или же unsigned long аргумент в вашем malloc вызов

void *p1 = malloc(10u);
void *p2 = malloc(10ul);

и вы заметите, что в одном из этих вызовов ваша шаблонная версия malloc также больше не "работает", вместо этого вызывается стандартный, так как он лучше соответствует аргументу (при условии, что на вашей платформе size_t определяется как либо unsigned int или же unsigned long)

Не отвечая на вопрос, который вы задали, но похоже, что вы пытаетесь сделать:

Если он доступен в вашей системе, вы можете использовать LD_PRELOAD для предварительной загрузки созданной вами библиотеки.so, в которой есть ваши версии malloc и бесплатно. Тогда они обязательно будут называться вместо стандартных версий.

Другие вопросы по тегам