Стоимость памяти таблицы виртуальных функций C ++ - PullRequest
12 голосов
/ 26 октября 2009

Рассмотрим:

class A
{
    public:
        virtual void update() = 0;
}

class B : public A
{
    public:
        void update() { /* stuff goes in here... */ }

    private:
        double a, b, c;
}

class C { 
  // Same kind of thing as B, but with different update function/data members
}

Я сейчас делаю:

A * array = new A[1000];
array[0] = new B();
array[1] = new C();
//etc., etc.

Если я вызываю sizeof(B), возвращаемый размер - это размер, требуемый 3 двойными членами, плюс некоторые накладные расходы, необходимые для таблицы указателей виртуальных функций. Теперь, возвращаясь к моему коду, получается, что sizeof (myclass) равен 32; то есть я использую 24 байта для своих элементов данных и 8 байтов для таблицы виртуальных функций (4 виртуальные функции). Мой вопрос: есть ли способ, как я могу упростить это? Моя программа в конечном итоге будет использовать чертовски много памяти, и мне не нравится, когда 25% ее потребляют указатели виртуальных функций.

Ответы [ 13 ]

1 голос
/ 26 октября 2009

Если вы заранее знаете все производные типы и их соответствующие функции обновления, вы можете сохранить производный тип в A и реализовать ручную диспетчеризацию для метода обновления.

Однако, как отмечают другие, вы действительно не платите так много за vtable, и компромиссом является сложность кода (и в зависимости от выравнивания вы можете вообще не экономить память!). Кроме того, если у любого из ваших членов данных есть деструктор, вам также придется беспокоиться о ручной отправке деструктора.

Если вы все еще хотите пойти по этому маршруту, он может выглядеть следующим образом:

class A;
void dispatch_update(A &);

class A
{
public:
    A(char derived_type)
      : m_derived_type(derived_type)
    {}
    void update()
    {
        dispatch_update(*this);
    }
    friend void dispatch_update(A &);
private:
    char m_derived_type;
};

class B : public A
{
public:
    B()
      : A('B')
    {}
    void update() { /* stuff goes in here... */ }

private:
    double a, b, c;
};

void dispatch_update(A &a)
{
    switch (a.m_derived_type)
    {
    case 'B':
        static_cast<B &> (a).update();
        break;
    // ...
    }
}
0 голосов
/ 27 октября 2009

Если вы действительно хотите сохранить память указателя виртуальной таблицы в каждом объекте, тогда вы можете реализовать код в стиле C ...

* 1003 Е.Г. *

struct Point2D {
int x,y;
};

struct Point3D {
int x,y,z;
};

void Draw2D(void *pThis)
{
  Point2D *p = (Point2D *) pThis;
  //do something 
}

void Draw3D(void *pThis)
{
  Point3D *p = (Point3D *) pThis;
 //do something 
}

int main()
{

    typedef void (*pDrawFunct[2])(void *);

     pDrawFunct p;
     Point2D pt2D;
     Point3D pt3D;   

     p[0] = &Draw2D;
     p[1] = &Draw3D;    

     p[0](&pt2D); //it will call Draw2D function
     p[1](&pt3D); //it will call Draw3D function
     return 0; 
}
0 голосов
/ 26 октября 2009

Учитывая все ответы, которые уже здесь, я думаю, что я сумасшедший, но мне это кажется правильным, поэтому я все равно публикую его. Когда я впервые увидел ваш пример кода, я подумал, что вы нарезаете экземпляры B и C, но потом я посмотрел немного ближе. Теперь я вполне уверен, что ваш пример не будет компилироваться вообще, но у меня нет компилятора в этом окне для тестирования.

A * array = new A[1000];
array[0] = new B();
array[1] = new C();

Для меня это выглядит так, будто первая строка выделяет массив 1000 A. Последующие две строки воздействуют на первый и второй элементы этого массива соответственно, которые являются экземплярами A, а не указателями на A. Таким образом, вы не можете назначить указатель на A этим элементам (а new B() возвращает такой указатель). Типы не одинаковы, поэтому он должен завершиться с ошибкой во время компиляции (если A не имеет оператора присваивания, который принимает A*, и в этом случае он будет делать то, что вы ему сказали).

Итак, я совсем не в базе? Я с нетерпением жду, чтобы узнать, что я пропустил.

...