Читая Александреску обратил снимание что он приводит пример
(аналогичный тому что ниже) и утверждает что будет вызвано
void testFunc(const Test &t) , однако у меня вызывается
void testFunc(double i), если ее убрать то да видны все достоинства
неявного преобразования типов. Вопрос шо за компилятор использует александреску?
#include "stdio.h"
class Test{
public:
/*explicit*/ Test(unsigned int i){
}
};
void testFunc(double i){
printf("Call i\n");
}
void testFunc(const Test &t){
printf("Call t\n");
}
int main()
{
int b = 0;
testFunc(b);
}
В приведенном тобой примере вызов функции разрешается встроенным преобразованием int -> double, так что конструктор Test(unsigned int i) даже не рассмартивается.
>Да, это многое объясняет.
Тем не менее вы меня не поняли, искать нужно книгу где написан
компилятор. А оригинальный пример вот:
----
Пример 1. Перегрузка. Пусть у нас есть, например, Widget::Widget(unsigned int),
который может быть вызван неявно, и функция Display, перегруженная
для Widget и double. Рассмотрим следующий сюрприз при разрешении
перегрузки:
void Display(double); // Вывод double
void Display(const Widget&); // Вывод Widget
Display(5); // Гм! Создание и вывод Widget
----
Вот ссылка: http://64.233.183.104/search?q=cache:HacoGLIY7RIJ:www.williamspublishing.com/PD
F/5-8459-0859-0/part.pdf
страница 85
>В приведенном тобой примере вызов функции разрешается встроенным
преобразованием int -> double, так что конструктор Test(unsigned
int i) даже не рассмартивается.
Расскажите это автору книги.
>>В приведенном тобой примере вызов функции разрешается встроенным преобразованием int -> double, так что конструктор Test(unsigned int i) даже не рассмартивается.
Да потому что в С++ все через жопу. И NULL тоже сделали не в виде (void*)0, как в Си, а как обычный 0, и все из-за того, что в С++ есть о$%#ть какое гениальное правило: указатель void * надо обязательно приводить к др. типу, хотя смысла в этом приведении вообще нет никакого, просто маразм на пустом месте. Поэтому нормальный NULL равный (void*)0 пришлось бы всегда приводить, например (char*) NULL. Я удивляюсь, почему великий Страуструп так не сделал! Он же любит подобные "штучки". Однако он предпочел просто 0, и я, написав NULL, получил не указатель, а ноль, и вызвал не ту ф-ию.
> Да потому что в С++ все через жопу. И NULL тоже сделали не в виде (void*)0, как в Си, а как обычный 0, и все из-за того, что в С++ есть о$%#ть какое гениальное правило: указатель void * надо обязательно приводить к др. типу, хотя смысла в этом приведении вообще нет никакого, просто маразм на пустом месте. Поэтому нормальный NULL равный (void*)0 пришлось бы всегда приводить, например (char*) NULL. Я удивляюсь, почему великий Страуструп так не сделал! Он же любит подобные "штучки". Однако он предпочел просто 0, и я, написав NULL, получил не указатель, а ноль, и вызвал не ту ф-ию.
Для дебилов, обьясним: ВСЕ ПРОСТО, в C++, в отличии от C, есть более чем 1 оператор приведения типа, имеющие разную логику работы:
reinterpret_cast
static_cast
dynamic_cast
const_cast
и еще несколько, в нестандартизованных (ныне) расширениях. Ведут они себя различным образом, и если например
reinterpret_cast больше всех похож на С-шное приведение типов, то например static_cast проверяет типобезопасность,
на этапе компиляции, а dynamic_cast вообще позволяет в рантайме определить, можно ли обьект прикастить к этому типу, или нет.
Особняком стоит const_cast, он нужен для "разконстанчивания" константных обьектов, но это нужно исключительно как
лекарство, нет, не от кривизны языка, а от кривизны быдла, не осилившего сразу написать правильный код. Таким образом
остается 3 приведения типа основных:
reinterpret -- простое наложение новой структуры, по указанному адресу, без каких-либо проверов времени компиляции/выполнения
static -- с проверкой типобезопасности на этапе компиляции
dynamic -- с runtime проверкой приводимости обьекта к указанному типу
От части такое разнообразие cast-ов вызвано некоторыми возможностями языка, напроч отсутствующими в других ООП ЯП
(например полноценное множественное наследование). А с другой стороны, это позволяет писать более
гибкий (полиморфный) код.
На последок, пример для тех, кто верит в (type*)obj:
$ cat 1.cpp
#include <stdio.h>
struct A { int a[10]; };
struct B { int b[0]; };
struct C : public A, B { };
int main()
{
C obj;
void *vpa = (void*)(A*)&obj;
void *vpb = (void*)(B*)&obj;
if (vpa != vpb)
{
printf("ptrdiff1=%d\n", (char*)vpb-(char*)vpa);
printf("ptrdiff2=%d\n", (C*)vpb-(C*)vpa);
}
}
$ ./1
ptrdiff1=40
ptrdiff2=1
> Да потому что в С++ все через жопу. И NULL тоже сделали не в виде (void*)0, как в Си, а как обычный 0, и все из-за того, что в С++ есть о$%#ть какое гениальное правило: указатель void * надо обязательно приводить к др. типу, хотя смысла в этом приведении вообще нет никакого, просто маразм на пустом месте. Поэтому нормальный NULL равный (void*)0 пришлось бы всегда приводить, например (char*) NULL. Я удивляюсь, почему великий Страуструп так не сделал! Он же любит подобные "штучки". Однако он предпочел просто 0, и я, написав NULL, получил не указатель, а ноль, и вызвал не ту ф-ию.
Ксате, ваш гневный выпад, - вдоль:
#include <stdio.h>
#include <stdlib.h>
struct A { int a; };
int main()
{
A *a = new A;
if (a == (void*)0)
{
printf("unreachable line reached!\n");
}
}
ЧЯДНТ? Для совсем уродов поясню: при сравнении указателя с void* указателем, срабатывает
operator void*
имеющийся у всех "простых" указателей, позволяющий привести любой не-воид указатель к воиду, его компилятор и находит,
и далее уже и не делает попыток привести void к обьекту.