Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Программирование и базы данных (http://forum.oszone.net/forumdisplay.php?f=21)
-   -   Присвоение функции значения (http://forum.oszone.net/showthread.php?t=122115)

mrcnn 06-11-2008 10:04 944667

Присвоение функции значения
 
Код:

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

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

И еще вопрос, как определяется
Код:

E* p;
p->L;

какую из имплементированных функций L вызвать? Где он их ищет?

pva 06-11-2008 12:14 944799

Цитата:

Цитата mrcnn
virtual BOOL L(int) = 0; »

это так называемая "чисто виртуальная функция", у которой нет описания (такая запись). Используется для описания абстрактных классов, для чего служит - описано в любом учебнике по ООП.
Цитата:

Цитата mrcnn
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 945078

Цитата:

в данном коде явная ошибка:
Код:

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 945564

Цитата:

Цитата mrcnn
по которому будет вызвана функция, стоящая за этим L, »

Но ведь L это функция и есть???

p->L это E::L

pva 07-11-2008 08:26 945622

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

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 и делает прыжок на функцию класса-наследника


Время: 07:40.

Время: 07:40.
© OSzone.net 2001-