Каково предпочтение разрешения имени функции / метода / шаблона в 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 и бесплатно. Тогда они обязательно будут называться вместо стандартных версий.