Выделение памяти без инициализации в C ++ - PullRequest
3 голосов
/ 26 декабря 2011

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

Проблема в том, что у меня есть класс BattlePoint, который выглядит примерно так:

class BattlePoint {
public:
    BattlePoint(int x, int y) : x(x), y(y) { };
    bool operator==(const BattlePoint &right);
    virtual ~BattlePoint();

private:
    int x, y;
};

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

BattleShip::BattleShip(BattlePoint start, enum shipTypeSize size, enum shipOrientation orientation) : size(size), orientation(orientation) {
    points = new BattlePoint[size]; // Here be doubts.
}

Итак, мне нужно, чтобы точка моего BattleShip содержала массив BattlePoints, каждая из которых имеет разные значения инициализации (например, 0,1; 0,2; 0,3 и т. Д.).

Вопрос: как я могу выделить неинициализированную память?

Julian

П.С .: Я не проводил никаких испытаний относительно работы new, я просто прочитал статью Википедии об этом , в которой говорится:

На языке программирования C ++, а также на многих языках, основанных на C ++ Языки, новый это языковая конструкция, которая динамически выделяет памяти в куче и инициализирует память, используя конструктор . За исключением формы под названием «размещение нового», новый пытается выделить достаточно памяти в куче для новых данных. Если успешно, он инициализирует память и возвращает адрес вновь выделенная и инициализированная память. Однако, если новый не может выделить памяти в куче он выдаст исключение типа std :: bad_alloc. Это устраняет необходимость явной проверки результата распределения. Вызов для удаления, который вызывает деструктор и возвращает память выделенный новым обратно в кучу, должен быть сделан для каждого вызова нового чтобы избежать утечки памяти.

Размещение нового должно быть решением, но оно не упоминает, как это сделать.

P.S. 2: я знаю, что это можно сделать с помощью векторного класса stdlib, но я специально избегаю этого.

Ответы [ 4 ]

7 голосов
/ 26 декабря 2011

Вам нужно использовать std::vector.В этом случае вы можете push_back, что хотите, например,

std::vector<BattlePoint> x;
x.push_back(BattlePoint(1, 2));

Если вы когда-либо обнаружите, что используете new[], delete или delete[], рефакторинг вашей программысразу убрать такое.Они ужасно небезопасны практически всеми возможными способами.Вместо этого используйте классы управления ресурсами, такие как std::unique_ptr, std::vector и std::shared_ptr.

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

Редактировать: Моя ошибка,Я не видел самую последнюю строку вашего вопроса.Обращаясь к нему:

PS 2: Я знаю, что это можно сделать с помощью векторного класса stdlib, но я намеренно избегаю этого.

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

4 голосов
/ 26 декабря 2011

очков = новая BattlePoint [размер]; // Здесь будут сомнения.
Постскриптум 2: я знаю, что это можно сделать с помощью векторного класса stdlib, но я намеренно избегаю этого.

Скорее всего, будут сомнения! Используйте std::vector. Почему бы тебе? Существует нет причин не использовать std::vector, особенно , если это решит вашу проблему.

std::vector<BattlePoint> bpoints;
bpoints.reserve(size); // there, only alloc'd memory, not initialized it.
bpoints.push_back(some_point); // still need to use push_back to initialize it

Уверен, придет вопрос - как std::vector только распределяет память ?! operator new ответ. Это оператор, который вызывается для выделения памяти при использовании new. new - для построения и инициализации, а operator new - для выделения (поэтому вы можете его перегрузить).

BattlePoint* bpoints = ::operator new(size); // happens in reserve
new (bpoints[index]) BattlePoint(some_x, some_y); // happens in push_back
3 голосов
/ 26 декабря 2011

Чтобы повторить вышеприведенные ответы, я бы, безусловно, указал вам на std::vector, так как это наилучшее возможное решение.Управление своими собственными динамическими массивами в C++ - почти - никогда не является хорошей идеей, а почти никогда не требуется.

Однако, чтобы ответить на прямой вопрос - вВ этой ситуации вы можете создать конструктор по умолчанию и некоторые мутаторы, чтобы получить желаемый эффект:

class BattlePoint {
public:
    // default constructor, default initialize to 0,0
    BattlePoint() x(0), y(0) {};

    BattlePoint(int x, int y) : x(x), y(y) { };
    bool operator==(const BattlePoint &right);
    virtual ~BattlePoint();

    // mutator functions allow you to modify the classes member values
    void set_x(int x_) {x = x_;}
    void set_y(int y_) {y = y_;}

private:
    int x, y;
};

Затем вы можете инициализировать это так, как вы привыкли в C:

BattlePoint* points = new BattlePoint[100];

for(int x = 0; x < 100; ++x)
{
   points->set_x(x);
   points->set_y(x * 2);
}

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

Так как вы спросили:)

Создайте свой класс BattlePoint снова сконструктор по умолчанию и мутаторы, однако на этот раз оставьте мутаторы закрытыми и объявите функцию-друг для их использования:

class BattlePoint {
public:
    // default constructor, default initialize to 0,0
    BattlePoint() x(0), y(0) {};

    BattlePoint(int x, int y) : x(x), y(y) { };
    bool operator==(const BattlePoint &right);
    virtual ~BattlePoint();

private:
    // mutator functions allow you to modify the classes member values
    void set_x(int x_) {x = x_;}
    void set_y(int y_) {y = y_;}

    int x, y;

    friend void do_initialize_x_y(BattlePoint*, int, int);
};

Создайте файл заголовка, который будет содержать локальную функцию для создания массива BattlePointобъекты.Эта функция будет доступна любому, кто включает заголовок, но при правильном названии «все» должны знать, что его не следует использовать.

// BattlePoint_Initialize.h
BattlePoint* create_battle_point_array(size_t count, int* x, int* y);

Эта функция определяется в файле реализации вместе с нашей функцией Friend.что мы будем «прятаться» от внешнего мира:

// BattlePoint_Initialize.cpp
#include <BattlePoint_Initialize.h>

namespace
{
    // by putting this function in an anonymous namespace it is only available
    // to this compilation unit.  This function can only be called from within
    // this particular file.
    //
    // technically, the symbols are still exported, but they are mangled badly
    // so someone could call this, but they would have to really try to do it
    // not something that could be done "by accident"
    void do_initialize_x_y(BattlePoint* bp, int x, int y)
    {
        bp->set_x(x);
        bp->set_y(y);
    }
}

// caution, relies on the assumption that count indicates the number of
// BattlePoint objects to be created, as well as the number of valid entries
// in the x and y arrays
BattlePoint* create_battle_point_array(size_t count, int* x, int* y)
{
    BattlePoint* bp_array = new BattlePoint[count];

    for(size_t curr = 0; curr < count; ++curr)
    {
        do_initialize_x_y(bp_array[curr], x[curr], y[curr]);
    }

    return bp_array;
}

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

Хотя create_battlepoint_array() теоретически можно вызывать где угодно, на самом деле он не способен изменять уже созданный объект BattlePoint.Функция do_initialize_x_y(), скрытая в анонимном namespace, спрятанном за кодом инициализации, не может быть легко вызвана откуда-либо еще в вашей программе.Фактически, если объект BattlePoint создан (и инициализирован в два этапа), он не может быть изменен далее.

2 голосов
/ 26 декабря 2011

FAQ по comp.lang.c ++ содержит полезные вещи, которые можно сказать по этому вопросу, в том числе попытки отговорить вас от использования нового места размещения - но если вы действительно настаиваете, у него есть полезный раздел на размещение нового и всех его подводных камней.

...