C ++ New vs Malloc для массива динамической памяти объектов

У меня есть класс Bullet, конструкция которого требует нескольких аргументов. Однако для их хранения я использую массив динамической памяти. Я использую C ++, поэтому хочу соответствовать стандарту, используя оператор new для выделения памяти. Проблема в том, что новый оператор запрашивает аргументы конструктора, когда я выделяю массив, которого у меня в то время нет. Я могу сделать это с помощью malloc, чтобы получить нужный размер, а затем заполнить форму, но это не то, что я хочу использовать :) какие-нибудь идеи?

pBulletArray = (Bullet*) malloc(iBulletArraySize * sizeof(Bullet)); // Works
pBulletArray = new Bullet[iBulletArraySize]; // Requires constructor arguments

Спасибо.


person Trent    schedule 14.03.2012    source источник
comment
Почему бы не подождать, пока вы будете готовы построить объект? Пока вы не построили Bullet, ваш Bullet* не будет указывать на Bullet.   -  person David Schwartz    schedule 14.03.2012
comment
Я не хочу, возможно, запрашивать новую память 20 раз в секунду.   -  person Trent    schedule 14.03.2012
comment
Как вызов конструктора влияет на то, сколько раз вам нужно выделять память? Похоже, в основе вашего вопроса лежит неверное предположение. (Кроме того, 20 выделений в секунду - это ничто на современных компьютерах. Тысячи выделений в секунду типичны для современного программного обеспечения. Рассмотрите возможность открытия такой веб-страницы, как эта, в браузере!)   -  person David Schwartz    schedule 14.03.2012
comment
@DavidSchwartz - я думаю, что OP создает пул пуль (журнал?), Чтобы можно было повторно использовать пули и, таким образом, избежать вызовов управления памятью во время запуска приложения. Хорошо, 20 вызовов в секунду - это не так много, но, возможно, диспетчер памяти уже сильно загружен (и это не только время, потраченное на вызов mm, это также любое время, потраченное на борьбу за блокировку). Кроме того, строительство само по себе может быть дорогостоящим по другим причинам, кроме выделения памяти (хотя здесь это, похоже, не так).   -  person Martin James    schedule 14.03.2012
comment
@Trent: Тебе нужно где-то их держать, прежде чем ты будешь готов их использовать, верно? Так почему бы просто не сделать это с помощью распределителя, поскольку его цель - удерживать неиспользуемую память до тех пор, пока вы не будете готовы ее использовать. Это звучит как чрезмерно сложное решение простой проблемы. Когда вам нужна память, выделите ее. Когда вы закончите с этим, освободите его. Это нужно где-то отслеживать, почему бы не использовать устройство, предназначенное для этой цели?   -  person David Schwartz    schedule 14.03.2012
comment
Есть веские причины использовать пулы объектов. Может ли это приложение эффективно использовать их для отслеживания только 20 пуль POD, это более открытый вопрос для обсуждения / расследования.   -  person Martin James    schedule 14.03.2012


Ответы (6)


Вы не можете.

И если вы действительно хотите соответствовать стандартам C ++, вам следует использовать std::vector.

К вашему сведению, это, вероятно, будет даже дороже, чем то, чего вы пытаетесь достичь. Если бы вы могли это сделать, new вызвал бы конструктор. Но поскольку вы все равно измените объект позже, первоначальная конструкция бесполезна.

person Luchian Grigore    schedule 14.03.2012
comment
Я передаю пуле все ее свойства (положение, заголовок и т. Д.), Когда она выстреливается, все, что я сейчас делаю, - это настраиваю некоторую память для вставки пуль. - person Trent; 14.03.2012
comment
Так что с новинками такое невозможно? - person Trent; 14.03.2012
comment
Спасибо за помощь, я избегаю векторов, сделаю это с помощью malloc. - person Trent; 14.03.2012
comment
@ Трент, ладно. Я отредактировал свой ответ, вы, наверное, все равно этого не захотите. - person Luchian Grigore; 14.03.2012
comment
Я полагаю, что невозможно предоставить ctor по умолчанию для целей распределения и создать Bullets на месте с помощью присваивания (RVO; см. Ответ на stackoverflow. com / q / 2323225/1214731)? - person tmpearce; 14.03.2012
comment
@Trent Почему вы избегаете std :: vector? - person Edward Loper; 14.03.2012
comment
@tmpearce конструктор по умолчанию создаст некоторые из ваших членов, что дороже, чем запрос памяти. Если у вас есть ученики, они тоже будут построены. - person Luchian Grigore; 14.03.2012
comment
@Luchiangrigore Верно, но поскольку это однократная инициализация, эта стоимость (должна) быть менее важной; Из вопроса казалось, что целью было избежать выделения памяти позже, «на лету». Вы правы, что выделение чистой памяти намного дешевле. - person tmpearce; 14.03.2012
comment
Я бы тоже не стал использовать std :: vector для пулов - я бы использовал ConcurrentQueue или BlockingCollection. - person Martin James; 14.03.2012
comment
@LuchianGrigore, в зависимости от членов, конструктор по умолчанию может избежать инициализации чего-либо и в основном работать без операции. - person bames53; 14.03.2012

1) std::vector

std::vector действительно является правильным способом сделать это в C ++.

std::vector<Bullet> bullets;
bullets.reserve(10); // allocate memory for bullets without constructing any

bullets.push_back(Bullet(10.2,"Bang")); // put a Bullet in the vector.
bullets.emplace_back(10.2,"Bang"); // (C++11 only) construct a Bullet in the vector without copying. 

2) оператор new []

Это также можно сделать с помощью new, но на самом деле этого не следует делать. Ручное управление ресурсами с помощью _6 _ / _ 7_ - сложная задача, аналогичная метапрограммированию шаблонов в том смысле, что ее лучше оставить разработчикам библиотек, которые будут использовать эти функции для создания эффективных высокоуровневых библиотек для вас. Фактически, чтобы сделать это правильно, вы в основном реализуете внутреннюю часть std::vector.

Когда вы используете оператор new для выделения массива, каждый элемент в массиве инициализируется по умолчанию. Ваш код мог бы работать, если бы вы добавили конструктор по умолчанию в Bullet:

class Bullet {
public:
    Bullet() {} // default constructor
    Bullet(double,std::string const &) {}
};

std::unique_ptr<Bullet[]> b = new Bullet[10]; // default construct 10 bullets

Затем, когда у вас есть реальные данные для Bullet, вы можете назначить их одному из элементов массива:

b[3] = Bullet(20.3,"Bang");

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


3) operator new

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

std::unique_ptr<Bullet,void(*)(Bullet*)> bullets(
    static_cast<Bullet*>(::operator new(10 * sizeof(Bullet))),
    [](Bullet *b){::operator delete(b);});

(Обратите внимание, что unique_ptr гарантирует, что хранилище будет освобождено, но не более того. В частности, если мы создаем какие-либо объекты в этом хранилище, мы должны вручную уничтожить их и сделать это безопасным способом.)

bullets теперь указывает на хранилище, достаточное для массива Bullets. В этом хранилище можно построить массив:

new (bullets.get()) Bullet[10];

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

AFAIK C ++ не определяет какой-либо четко определенный метод построения массива без создания элементов. Я полагаю, что это во многом связано с тем, что это было бы недопустимо для большинства (всех?) Реализаций C ++. Итак, хотя следующее технически не определено, на практике оно довольно хорошо определено.

bool constructed[10] = {}; // a place to mark which elements are constructed

// construct some elements of the array
for(int i=0;i<10;i+=2) {
    try {
        // pretend bullets points to the first element of a valid array. Otherwise 'bullets.get()+i' is undefined
        new (bullets.get()+i) Bullet(10.2,"Bang");
        constructed = true;
    } catch(...) {}
}

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

// destruct the elements of the array that we constructed before
for(int i=0;i<10;++i) {
    if(constructed[i]) {
        bullets[i].~Bullet();
    }
}

// unique_ptr destructor will take care of deallocating the storage 

Вышеупомянутое - довольно простой случай. Сложнее сделать нетривиальное использование исключений этого метода безопасным, не заключая все это в класс. Обобщение этого класса в основном означает реализацию std::vector.


4) std::vector

Так что просто используйте std::vector.

person bames53    schedule 14.03.2012

Возможно делать то, что вы хотите - ищите "оператор новый", если вы действительно хотите знать, как это сделать. Но это почти наверняка плохая идея. Вместо этого используйте std :: vector, который позаботится обо всех раздражающих деталях за вас. Вы можете использовать std :: vector :: reserve для заблаговременного выделения всей памяти, которую вы будете использовать.

person Edward Loper    schedule 14.03.2012

Bullet** pBulletArray = new Bullet*[iBulletArraySize];

Затем заполните pBulletArray:

for(int i = 0; i < iBulletArraySize; i++)
{
   pBulletArray[i] = new Bullet(arg0, arg1);
}

Только не забудьте потом освободить память, используя команду delete.

person TVOHM    schedule 14.03.2012
comment
@Jesse, я думаю, настоящий вопрос будет в том, понял ли он вопрос? - person Luchian Grigore; 14.03.2012

Обычно в C ++ new выделяется память для экземпляра класса, а затем вызывается конструктор для этого экземпляра. По сути, вы уже выделили память для своих экземпляров.

Вы можете вызвать только конструктор для первого экземпляра следующим образом:

new((void*)pBulletArray) Bullet(int foo);

Вызов конструктора второго будет выглядеть так (и т. Д.)

new((void*)pBulletArray+1) Bullet(int bar);

если конструктор Bullet принимает int.

person Daniel Schlößer    schedule 14.03.2012

Если на самом деле вам нужно просто быстрое выделение / освобождение, то вам следует изучить «пулы памяти». Я бы рекомендовал использовать реализацию boost. вместо того, чтобы пытаться свернуть свою собственную. В частности, вы, вероятно, захотите использовать "object_pool".

person Edward Loper    schedule 15.03.2012