Misguided
Misguided

Reputation: 1302

Allocating memory without initializing it in C++

I'm getting acquainted with C++, and I'm having a problem with memory management. In C, whenever I'd want to reserve memory for any number of elements, regardless of type, I would just call malloc() and then initialize by hand (through a loop), to whichever value I wanted. With C++'s new, everything is automagically initialized.

Problem is, I've got a BattlePoint class which goes a little something like this:

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

private:
    int x, y;
};

As you can see, it takes a few x and y values through the initializer and then sets its own x and y from it. The problem is, this function will be called from a function which will allocate an array of them:

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

So, I need my BattleShip's point to hold an array of BattlePoints, each one with different initialization values (such as 0,1; 0,2; 0,3, etcetera).

Question is: how could I allocate my memory uninitialized?

Julian,

P.S.: I haven't done any testing regarding the way new works, I simple read Wikipedia's article on it which says:

In the C++ programming language, as well as in many C++-based languages, new is a language construct that dynamically allocates memory on the heap and initialises the memory using the constructor. Except for a form called the "placement new", new attempts to allocate enough memory on the heap for the new data. If successful, it initialises the memory and returns the address to the newly allocated and initialised memory. However if new cannot allocate memory on the heap it will throw an exception of type std::bad_alloc. This removes the need to explicitly check the result of an allocation. A call to delete, which calls the destructor and returns the memory allocated by new back to the heap, must be made for every call to new to avoid a memory leak.

placement new should be the solution, yet it makes no mention on how to do it.

P.S. 2: I know this can be done through stdlib's vector class, but I'm avoiding it on purpose.

Upvotes: 4

Views: 5978

Answers (4)

Chad
Chad

Reputation: 19032

To echo the above answers, I would most certainly point you towards std::vector as it is the best possible solution. Managing your own dynamic arrays in C++ is almost never a good idea, and is almost never necessary.

However, to answer the direct question -- in this situation you can create a default constructor and some mutators to get the desired effect:

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;
};

Then you can initialize this as you are used to in C:

BattlePoint* points = new BattlePoint[100];

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

If you're bothered by basically making the BattlePoint class publically mutable, you can keep the mutators private and introduce a friend function specifically for initializing the values. This is a slightly more involved concept, so I'll forgo further explanation on this for now, unless it is needed.

Since you asked :)

Create your BattlePoint class again with a default constructor and mutators, however this time leave the mutators private, and declare a friend function to use them:

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);
};

Create a header file that will contain a local function for creating the array of BattlePoint objects. This function will be available to anyone that includes the header, but if named properly then "everyone" should know not to use it.

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

This function gets defined in the implementation file, along with our friend function that we will "hide" from the outside world:

// 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;
}

So there you have it. A very convoluted way to meet your basic requirements.

While, create_battlepoint_array() could in theory be called anywhere, it's actually not capable of modifying an already created BattlePoint object. The do_initialize_x_y() function by nature of being hidden in an anonymous namespace tucked away behind the initialization code cannot easily be called from anywhere else in your program. In effect, once a BattlePoint object has been created (and initialized in two steps), it cannot be modified further.

Upvotes: 2

Puppy
Puppy

Reputation: 146910

You need to use a std::vector. In this case you can push_back whatever you want, e.g.

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

If you ever find yourself using new[], delete, or delete[], refactor your program immediately to remove such. They are hideously unsafe in virtually every way imaginable. Instead, use resource-managing classes, such as std::unique_ptr, std::vector, and std::shared_ptr.

Regular new can be useful in some situations involving unique_ptr, but else avoid it. In addition, placement new is usually not worth it. Of course, if you're writing a resource-managing class, then you may have to use them as underlying primitives, but that's few and very far between.

Edit: My mistake, I didn't see the very last line of your question. Addressing it:

P.S. 2: I know this can be done through stdlib's vector class, but I'm avoiding it on purpose.

If you have some campaign against the Standard Library, then roll your own vector replacement. But do not go without a vector class. There's a reason that it must be provided by all conforming compilers.

Upvotes: 5

crazyscot
crazyscot

Reputation: 11989

The comp.lang.c++ FAQ has useful things to say on the matter, including attempting to dissuade you from using placement new - but if you really insist, it does have a useful section on placement new and all its pitfalls.

Upvotes: 2

Xeo
Xeo

Reputation: 131789

points = new BattlePoint[size]; // Here be doubts.
P.S. 2: I know this can be done through stdlib's vector class, but I'm avoiding it on purpose.

Most certainly there will be doubts! Use std::vector. Why wouldn't you? There is no reason not to use std::vector, especially if it solves your problem.

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

I'm sure the question will come - how does std::vector only alloc the memory?! operator new is the answer. It's the operator that gets called for memory allocation when you use new. new is for construction and initialization, while operator new is for allocation (that's why you can overload it).

BattlePoint* bpoints = ::operator new(size); // happens in reserve
new (bpoints[index]) BattlePoint(some_x, some_y); // happens in push_back

Upvotes: 4

Related Questions