C ++ массив производного класса против массива указателей базового класса на производные объекты - почему объем памяти выделяется так сильно? - PullRequest
2 голосов
/ 18 апреля 2011

Мне нужны некоторые разъяснения по проблеме, которую я не совсем понимаю. Используя два следующих сценария, я бы подумал, что объем выделенной памяти будет примерно одинаковым. Тем не менее, сценарий 2 дает мне исключение bad_alloc через некоторое время и, похоже, жует память как сумасшедший (используя диспетчер задач Windows в качестве прокси для объема памяти, выделенного для процесса). Следующее скомпилировано в Windows 32bit с использованием MSVC10.

Скажите, у меня есть следующий базовый класс:

template<class T>
class Base
{
protected:
    T x;
public:
    Base() {}
    Base(T _x) : x(_x){}
    virtual bool func() = 0;
};

Теперь, что касается производного класса:

template<class T>
class Derived : public Base<T>
{
public:
    Derived() {}
    Derived(T _x) :  Base(_x){}
    bool func() { return true; };
};

Теперь рассмотрим два случая. Во-первых, выделите динамический массив класса Derived и заполните его производными объектами, например:

int main()
{
    int size = SOME_LARGE_NUMBER;
    Derived<int>* d = new Derived<int>[size];
    for (int i = 0; i < size; i++)
    {
        d[i] = Derived<int>(i);
    }

    // delete here
}

Во-вторых, выделите динамический массив указателей базового класса и сделайте так, чтобы они указывали на фактические экземпляры класса Derived, например:

int main()
{
    int size = SOME_LARGE_NUMBER;
    Base<int>** d = new Base<int>*[size];
    for (int i = 0; i < size; i++)
    {
        d[i] = new Derived<int>(i);
    }

    // delete here
}

Я установил SOME_LARGE_NUMBER в любом сценарии на 40 000 000. В первом сценарии программа завершается нормально - во втором я получаю исключение bad_alloc. Я задаюсь вопросом, является ли это ожидаемым поведением или я что-то упускаю здесь? Если это так, что может быть лучше? Обратите внимание, что я получаю ту же проблему, используя vector<Base<int>*> и boost::ptr_vector<Base<int>>.

Ответы [ 5 ]

6 голосов
/ 18 апреля 2011

Что ж, если вы выделяете в одном блоке, требуется size размер, умноженный на Derived, и небольшие накладные расходы для одного выделения.Но если вы выделяете отдельно, для него требуется size раз размер указателя (4 байта), плюс size размер Derived и size (плюс один) раз накладных расходов на выделение, что по крайней мере8 байт.Если вы близки к краю, то дополнительные 12 байтов size вполне могут быть разницей между тем, что все еще умещается в памяти, а не нет.

Я не скажу вам наверняка, что издержки распределителя составляют ровно 8 байтов., но сделать его менее 2 указателей было бы довольно сложно, и маловероятно, что стандартный распределитель Microsoft настолько сложен.

Обратите внимание, что, поскольку std::vector и boost::ptr_vector делают точно так же, как вы вручнуюони терпят неудачу так же.Они являются просто обертками, чтобы гарантировать, что удаление будет вызываться там, где это необходимо, и ничего более.

Все накладные расходы будут удвоены для 64-битных целей.Указатель составляет 8 байтов, а издержки распределителя обычно составляют 2 указателя.

4 голосов
/ 18 апреля 2011

Во втором примере вы выделяете память для хранения как указателей на объекты, так и самих объектов, что само по себе использует как минимум:

(sizeof(Base<int>*)+sizeof(Derived<int>))*size

В зависимости от размера указателя на вашей платформе это может привести кпо сравнению с первым примером это требует больших затрат ресурсовсоответствует sizeof (производное ).

0 голосов
/ 18 апреля 2011

РЕДАКТИРОВАТЬ: я не читал код хорошо, кажется, что издержки во втором примере в двойном выделении памяти один для указателя, а другой для самого объекта

0 голосов
/ 18 апреля 2011

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

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

Извините, я не могу предложить больше.

0 голосов
/ 18 апреля 2011

Я думаю, что в первом случае у вас недостаточно памяти в одном блоке.Во втором случае у вас достаточно памяти для массива указателей и достаточно памяти, чтобы выделить объектам различные места / блоки в памяти.

...