PDA

Показать полную графическую версию : Присвоение функции значения


mrcnn
06-11-2008, 10:04
class E
{
public:
virtual BOOL L(int) = 0;
};

L - функция
Что означает "= 0" и правильно ли это?

И еще вопрос, как определяется
E* p;
p->L;
какую из имплементированных функций L вызвать? Где он их ищет?

pva
06-11-2008, 12:14
virtual BOOL L(int) = 0; »
это так называемая "чисто виртуальная функция", у которой нет описания (такая запись). Используется для описания абстрактных классов, для чего служит - описано в любом учебнике по ООП.E* p;
p->L; » в данном коде явная ошибка: создан указатель, который смотрит на случайный адрес, потом пытаются взять адрес виртуальной функции, как если бы адресу указателя располагался экземпляр класса E. Причём адрес нигде не используется. Правильней было бы сделать так (если я понял что имеется ввиду):

class E_implemetiation : public E
{
public:
bool L(int n)
{
return (std::cout << "L(" << n << ")").good();
}
}

...

E_implementation E_implementation1;
E* p = &E_implementation1;

...

p->L(1);

mrcnn
06-11-2008, 16:37
в данном коде явная ошибка:
E* p;
p->L;

Это не реальный код, а теоретический. Все имена - теоретические. Первой строкой я хотел показать, что это указатель на класс E и ничего больше. Надо было добавить многоточия? Предполагалось, что он уже инициализирован нормальным адресом класса при обращении к нему во второй строке.
Затем производится обращение к чистой виртуальной функции. Я не совсем понимаю механизм, по которому будет вызвана функция, стоящая за этим L, но функция, которая будет вызвана, определена, где-то в другом месте. Переопределять L в этом классе вообще нельзя. Надо переопределять то, что стоит за этим L. Но как допустим найти, какая именно? Я не понимаю взаимосвязи между вот этой чисто виртуальной функцией, и той функцией, которая будет вызвана при обращении к ней, т.е. p->L.
Класс E не является наследником других классов, но у него есть наследники.

В реальном рабочем коде примерно следующее.



class E
{
public:
virtual BOOL L(int) = 0;
...
};


class T : public R, public E
{
...
}

class C : public E
{
public:
...
};

class F
{

public:
virtual BOOL LT(const z&, E*);
};



BOOL F::LT(const z& p, E* px)
{
if(!px || !px->L(1)) // что за функция L будет вызвана?
return FALSE;
}

___oj
07-11-2008, 03:44
по которому будет вызвана функция, стоящая за этим L, »
Но ведь L это функция и есть???

p->L это E::L

pva
07-11-2008, 08:26
Обрисую как это устроено. Любой полиморфный класс (то есть содержащий виртуальные функции) содержит внутреннюю переменную - указатель на таблицу виртуальных функций.

class E
{
private: virtual void mem_fun()
{
// do something
}
public: virtual bool L(int)
{
// do something
}
}

// кодируется как (условный код):
struct E_vtbl
{
int base_offset = 0;
mem_fun_address = &mem_fun; // адрес кода mem_fun
L_address = &L; // адрес кода L
}

struct E_class
{
E_vtbl* _vtbl = &E_vtbl;
}

// таким образом вызов виртуальной функции идёт так:
// p->mem_fun();
(*p->_vtbl->mem_fun_address)(p + p->_vtbl->base_offset);

// p->L(1);
(*p->_vtbl->L_address)(p + p->_vtbl->base_offset, 1);

У чистых виртуальных функций считается что указатель в виртуальной таблице является нулём (оттуда и запись), но на самом деле бывает что такой таблицы вообще нет, а бывает, что там находится код, который выдаёт сообщение об ошибке "pure virtual function called". Таким образом:

class E
{
int a;
public: virtual bool L(int) = 0;
}

class E1 : public E
{
int b;
public: virtual bool L(int)
{
cout << "E1::L\n";
}
}

class E2 : public E
{
double c;
public: virtual bool L(int)
{
cout << "E2::L\n";
}
}

// кодируется как:
struct E1_vtbl
{
base_offset = 0;
L_address = &E1::L // E_vtbl, заполненная правильным адресом E1::L
}

struct E1_vtbl_E
{
base_offset = -4;
L_address = &E1::L // E_vtbl, заполненная правильным адресом E1::L
}

struct E2_vtbl
{
base_offset = 0;
L_address = &E2::L // E_vtbl, заполненная правильным адресом E2::L
}

struct E2_vtbl_E
{
base_offset = -4;
L_address = &E2::L // E_vtbl, заполненная правильным адресом E2::L
}

struct E1_class
{
// общие для всех
E1_vtbl* _vtbl = &E1_vtbl; // таблица E1
// составляющий класс E
E_vtbl* _vtbl1 = &E1_vtbl_E; // таблица E
int a; // данные E

int b; // общие данные
}

struct E2_class
{
// общие для всех
E2_vtbl* _vtbl = &E2_vtbl; // таблица E2
// составляющий класс E
E_vtbl* _vtbl1 = &E2_vtbl_E; // таблица E
int a; // данные E

double c; // общие данные
}

1. преобразование указателя к E - вычисление адреса, где находится составляющий класс: E_ptr = E2_ptr + 4;
2. вызов E::L - из виртуальной таблицы (*E_ptr->_vtbl->L)(E_ptr+E_ptr->_vtbl->base_offset, 1). В результате, если посчитать, вызов преобразуется в E2_ptr->E::L2
Для чего нужен base_offset? Дело в том, что в C++ классы обладают множественным виртуальным наследием. Чтобы программа могла при реализации вызова могла преобразовать адрес к наследнику от любого базового класса (который может и не "знать", в состав чего входит). Иногда base_offset вводят в тело функции. Идёт вызов кода, который вносит изменения в указатель this и делает прыжок на функцию класса-наследника




© OSzone.net 2001-2012