Память таблицы виртуальной функции C++ стоится

Набор Компонента (propietary) : Набор Компонента Комплекта
DevExpress QuantumGrid (Свободный) : ДЖЕДАЙ и эксперты JCS
IDE : система управления версиями GExperts
: ловец/журнал JVCS/Free VCS
Исключения : комплект EurekaLog
Локализации: siComponents TsiLang

13
задан Beryllium 4 March 2015 в 08:24
поделиться

13 ответов

V-таблица составляет для каждого класса , а не для каждого объекта. Каждый объект содержит только указатель на свою v-таблицу. Таким образом, накладные расходы на экземпляр составляют sizeof (указатель) (обычно 4 или 8 байтов). Не имеет значения, сколько виртуальных функций у вас есть для размера объекта класса. Учитывая это, я думаю, вам не стоит особо об этом беспокоиться.

43
ответ дан 1 December 2019 в 17:14
поделиться

У вас есть два варианта.

1) Не беспокойтесь об этом.

2) Не используйте виртуальные функции. Однако отказ от использования виртуальных функций может просто изменить размер кода, поскольку код становится более сложным.

6
ответ дан 1 December 2019 в 17:14
поделиться

Стоимость пространства vtable составляет один указатель (выравнивание по модулю). Сама таблица не помещается в каждый экземпляр класса.

6
ответ дан 1 December 2019 в 17:14
поделиться

Отказ от проблемы с указателем vtable в вашем объекте:

В вашем коде есть другие проблемы:

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

Проблема, с которой вы столкнулись, - это проблема среза.
Вы не можете поместить объект класса B в пространство, размер которого зарезервирован для объекта класса A.
Вы просто отрежете часть B (или C) объекта, оставив вам только часть A.

Что вы хотите сделать. У него есть массив указателей A, чтобы он удерживал каждый элемент по указателю.

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

Теперь у вас есть другая проблема разрушения. В порядке. Это могло продолжаться веками.
Краткий ответ используйте boost: ptr_vector <>

boost:ptr_vector<A>  array(1000);
array[0] = new B();
array[1] = new C();

Никогда не выделяйте такой массив, если вам не нужно (это тоже Java Like, чтобы быть полезным).

5
ответ дан 1 December 2019 в 17:14
поделиться

Как правило, каждый экземпляр класса с хотя бы одной виртуальной функцией будет иметь дополнительный указатель, сохраненный с его явными членами-данными.

Этого не избежать, но помните, что (опять же обычно) каждая таблица виртуальных функций является распределяется между всеми экземплярами класса, поэтому нет больших накладных расходов на наличие нескольких виртуальных функций или дополнительных уровней наследования после уплаты «налога на vptr» (небольшая стоимость указателя на vtable).

Для больших классов накладные расходы становится намного меньше в процентах.

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

11
ответ дан 1 December 2019 в 17:14
поделиться

Если вы заранее знаете все производные типы и их соответствующие функции обновления, вы можете сохранить производный тип в 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;
    // ...
    }
}
1
ответ дан 1 December 2019 в 17:14
поделиться

Это не ответ на вопрос напрямую, но также учтите, что порядок объявления ваших членов данных может увеличить или уменьшить ваше реальное потребление памяти для каждого объекта класса. Это связано с тем, что большинство компиляторов не могут (читай: не делать) оптимизировать порядок, в котором члены класса размещаются в памяти, чтобы уменьшить внутреннюю фрагментацию из-за проблем с выравниванием.

1
ответ дан 1 December 2019 в 17:14
поделиться

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

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

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

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

0
ответ дан 1 December 2019 в 17:14
поделиться

Сколько экземпляров производных от A классов вы ожидаете? Сколько различных классов, производных от A, вы ожидаете?

Обратите внимание, что даже с миллионом экземпляров мы говорим о 32 МБ. До 10 миллионов, не переживайте.

Обычно вам нужен дополнительный указатель на каждый экземпляр (если вы работаете на 32-битной платформе, последние 4 байта связаны с выравниванием). Каждый класс потребляет дополнительные (количество виртуальных функций * sizeof (указатель виртуальной функции) + фиксированный размер) байтов для своего VMT.

Обратите внимание, что, учитывая выравнивание для двойников, даже один байт в качестве идентификатора типа приведет к увеличению размера элемента массива до 32. Таким образом, решение Степан Райко полезно в некоторых случаях, но не в вашем.

Также не забывайте о накладных расходах общей кучи для такого большого количества мелких объектов. У вас может быть еще 8 байтов на объект.

2
ответ дан 1 December 2019 в 17:14
поделиться

Вы добавляете один указатель на виртуальную таблицу для каждого объекта - если вы добавите несколько новых виртуальных функций, размер каждого объекта не увеличится. Обратите внимание, что даже если вы используете 32-битную платформу, где указатели составляют 4 байта, вы видите, что размер объекта увеличивается на 8, вероятно, из-за общих требований к выравниванию структуры (т. Е. Вы получаете 4 байтов заполнения).

Таким образом, даже если вы сделали класс не виртуальным, добавление одного члена char, вероятно, добавило бы целых 8 байтов к размеру каждого объекта.

Я думаю, что единственный способ уменьшить размер ваших объектов - это:

  • сделать их не виртуальными (вам действительно нужно полиморфное поведение?)
  • использовать float вместо double для одного или нескольких элементов данных, если вам не нужна точность
  • , если вы ' Если вы, вероятно, увидите много объектов с одинаковыми значениями для членов данных, вы можете сэкономить на пространстве памяти в обмен на некоторую сложность в управлении объектами, используя шаблон проектирования Легковес
1
ответ дан 1 December 2019 в 17:14
поделиться

Если у вас будут миллионы этих вещей, а память вызывает у вас серьезную озабоченность, то вам, вероятно, не следует делать их объектами. Просто объявите их как структуру или массив из 3 двойников (или что-то еще) и поместите функции для управления данными в другое место.

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

Вероятно, что у вас будут большие группы объектов одного типа? В этом случае вы можете поместить информацию о типе на один уровень выше по сравнению с отдельными классами «A» ...

Что-то вроде:

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

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

    private:
        vector<double[3]> points;
}

class C_collection { /* Same kind of thing as B_collection, but with different update function/data members */
2
ответ дан 1 December 2019 в 17:14
поделиться

Как уже было сказано, в типичном популярном подходе к реализации, когда класс становится полиморфным, каждый экземпляр увеличивается на размер обычного указателя данных. Неважно, сколько виртуальных функций у вас есть в вашем классе. На 64-битной платформе размер увеличится на 8 байт. Если вы наблюдали 8-байтовый рост на 32-битной платформе, это могло быть вызвано добавлением дополнения к 4-байтовому указателю для выравнивания (если ваш класс требует 8-байтового выравнивания).

Кроме того, это, вероятно, стоит того. отмечая, что виртуальное наследование может вводить дополнительные указатели данных в экземпляры классов (виртуальные базовые указатели). Я знаком только с несколькими реализациями, и по крайней мере в одной количество виртуальных базовых указателей было таким же, как количество виртуальных баз в классе,

1
ответ дан 1 December 2019 в 17:14
поделиться

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

Например

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; 
}
-1
ответ дан 1 December 2019 в 17:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: