Распределение памяти и унаследованные классы в C ++ - PullRequest
2 голосов
/ 19 февраля 2010

Скажите, у меня есть эти структуры:

struct Base{
 ...
}

struct Derived:public Base{
 //everything Base contains and some more
}

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

void doStuff(Base *data, unsigned int numItems){
 Base *newdata = new Base[numItems];
 memcpy(newdata, data, numItems*sizeof(Base));
 ...
 delete [] newdata;
}

Но если бы я использовал эту функцию так:

Base *data = new Derived[100];
doStuff(data, 100);

Это не сработает, не так ли? Поскольку Derived1 больше, чем Base, поэтому для выделения Base недостаточно памяти?

Ответы [ 6 ]

4 голосов
/ 19 февраля 2010

Точно. Это вариация проблемы нарезки .

2 голосов
/ 19 февраля 2010

Вам нужно будет использовать указатели и использовать конструкторы копирования.Кроме того, не используйте ключевое слово struct для более чем основных структур данных.Технически это работает, но то, что вы создаете, это иерархия классов, поэтому используйте ключевое слово class.

Это не будет работать просто потому, что Derived больше, а также для намерений и целей является совершенно другим объектом, которыйсовместим с Base в основном по интерфейсу, но, что более важно, при работе с классами не следует использовать низкоуровневые манипуляции с памятью.Вместо этого вам следует настроить конструкторы копирования и использовать библиотеки, такие как <алгоритм>, для выполнения шаблонных действий с ними.

Более того, причина, по которой это не будет работать, несмотря на то, что это законноСинтаксис (т. е. Base * = Derived *) заключается в том, что вы выделяете объекты большего размера, чем индексировал бы Base *, что может привести к повреждению памяти при записи памяти в неправильное местоположение.

Например, если a Base объект имеет размер 4 байта, C ++ будет индексировать массив каждые четыре байта, но если фактические выделенные объекты Derived имеют размер 8 байтов, то вы выполняете индексирование на полпути через границы объекта, и ваши переменные-члены не будут указывать на правильное местоположениев памяти.

Использование иерархий классов в массиве:

Base *objects[100];
for (int i = 0; i < 100; i++)
    objects[i] = new Derived();

Еще больше , чтобы упростить управление, вы можете использовать механизм интеллектуальных указателейи список шаблонов вместо необработанных указателей.

1 голос
/ 19 февраля 2010

ДА! Ты прав. Это не сработает. Поскольку Derived1 больше, чем Base, поэтому для выделения Base недостаточно памяти.

0 голосов
/ 19 февраля 2010

Ну, массив Derived не является массивом Base .

Если вам нужно повысить Derived* до Base*, вы должны выделить массив указателей на Base или, предпочтительно, vector<Base*>

vector<Base*> data(100);
// Initialize the elements
for (vector<Base*>::iterator it = data.begin(); it != data.end(); ++it)
{
    *it = new Derived;
}

doStuff(data);

// Destroy the elements
for (vector<Base*>::iterator it = data.begin(); it != data.end(); ++it)
{
    delete *it;
}

И ваша doStuff функция становится:

void doStuff(const vector<Base*>& data)
{
    // Copy the objects, not the pointers
    vector<Base*> newdata;
    for (vector<Base*>::const_iterator it = data.begin();
         it != data.end(); ++it)
    {
        newdata.push_back((*it)->clone());
    }

    // Do stuff

    // Destroy the copies
    for (vector<Base*>::iterator it = newdata.begin();
         it != newdata.end(); ++it)
    {
        delete *it;
    }
}

Обратите внимание, что для копирования объектов, не зная, являются ли они Base или Derived, нам нужно использовать идиому виртуального конструктора . Требуется изменить Base и Derived следующим образом:

struct Base{
    ...
    virtual Base* clone() const { return new Base(*this); }
    virtual ~Base() {}
};

struct Derived : public Base {
    ...
    Derived* clone() const { return new Derived(*this); }
};
0 голосов
/ 19 февраля 2010

Да. Объем памяти в Derived больше, чем объем памяти для Base, поэтому копия не будет работать должным образом.

0 голосов
/ 19 февраля 2010

Вы можете легко сделать это с помощью шаблона:

template< class T >void doStuff(T *data, unsigned int numItems)
{
    T *newdata = new T[numItems];
    memcpy( newdata, data, sizeof( T ) * numItems );
    ...
    delete [] newdata;
}

Редактируйте в соответствии с комментариями: если вы хотите сделать это для смешанной коллекции, все станет намного сложнее ... одно из возможных решений это:

struct Base{
    virtual Base* CopyTo()      { return new Base( *this ); }
};

struct Derived:public Base{
    virtual Derived* CopyTo()   { return new Derived( *this ); }

};

void doStuff( Base** ppArray, int numItems )
{
    Base** ppNewArray   = new Base*[numItems];
    int count = 0;
    while( count < numItems )
    {
        ppNewArray[count] = ppArray[count]->CopyTo();
        count++;
    }

    // do stuff

    count = 0;
    while( count < numItems )
    {
        delete ppNewArray[count];
        count++;
    }
    delete[] ppNewArray;
}
...