Fabien
Fabien

Reputation: 13416

In C++11, how can I call new and reserve enough memory for the object?

I have a class that is described that way :

class Foo {
    int size;
    int data[0];

public:
    Foo(int _size, int* _data) : size(_size) {
        for (int i = 0 ; i < size ; i++) {
            data[i] = adapt(_data[i]);
        }
    }

    // Other, uninteresting methods
}

I cannot change the design of that class.

How can I create an instance of that class ? Before calling the constructor, I have to make it reserve enough memory to store its data, so it has to be on the heap, not on the stack. I guess I want something like

Foo* place = static_cast<Foo*>(malloc(sizeof(int) + sizeof(int) * size));
*place = new Foo(size, data);  // I mean : "use the memory allocated in place to do your stuff !"

But I can't find a way to make it work.

EDIT : as commentators have noticed, this is not a very good overall design (with non-standards tricks such as data[0]), alas this is a library I am forced to use...

Upvotes: 8

Views: 961

Answers (4)

Alexander Oh
Alexander Oh

Reputation: 25641

I think a good C++ solution is to get raw memory with new and then use placement new to embed your class into it.

getting raw memory works like this:

Foo *f = static_cast<Foo *>(operator new(sizeof(Foo)); 

constructing the object in received memory works like this:

new (f) Foo(size, data); // placement new

remember that this also means that you have to manually clean up the place.

f->~Foo(); // call the destructor
operator delete(f); // free the memory again

My personal opinion is, that it is bad to use malloc and free in newly written C++ code.

Upvotes: 1

zakinster
zakinster

Reputation: 10688

You could malloc the memory for the object and then use a placement new to create the object in the previously allocated memory :

void* memory = malloc(sizeof(Foo) + sizeof(int) * size);
Foo* foo = new (memory) Foo(size, data);

Note that in order to destroy this object, you can't use delete. You would have to manually call the destructor and then use free on the memory allocated with malloc :

foo->~Foo();
free(memory); //or free(foo);

Also note that, as @Nikos C. and @GManNickG suggested, you can do the same in a more C++ way using ::operator new :

void* memory = ::operator new(sizeof(Foo) + sizeof(int) * size);
Foo* foo = new (memory) Foo(size, data);
...
foo->~Foo();
::operator delete(memory); //or ::operator delete(foo);

Upvotes: 10

Sebastian Redl
Sebastian Redl

Reputation: 71989

You have a library that does this thing but doesn't supply a factory function? For shame!

Anyway, while zakinster's method is right (I'd directly call operator new instead of newing an array of chars, though), it's also error-prone, so you should wrap it up.

struct raw_delete {
  void operator ()(void* ptr) {
    ::operator delete(ptr);
  }
};

template <typename T>
struct destroy_and_delete {
  void operator ()(T* ptr) {
    if (ptr) {
      ptr->~T();
      ::operator delete(ptr);
    }
  }
};
template <typename T>
using dd_unique_ptr = std::unique_ptr<T, destroy_and_delete<T>>;

using FooUniquePtr = dd_unique_ptr<Foo>;

FooUniquePtr CreateFoo(int* data, int size) {
  std::unique_ptr<void, raw_delete> memory{
    ::operator new(sizeof(Foo) + size * sizeof(int))
  };
  Foo* result = new (memory.get()) Foo(size, data);
  memory.release();
  return FooUniquePtr{result};
}

Yes, there's a bit of overhead here, but most of this stuff is reusable.

Upvotes: 8

NtscCobalt
NtscCobalt

Reputation: 1689

If you really want to be lazy simply use a std::vector<Foo>. It will use more space (I think 3 pointers instead of 1) but you get all the benefits of a container and really no downsides if you know it is never going to change in size.

Your objects will be movable given your definition so you can safely do the following to eliminate reallocation of the vector during initial fill...

auto initialFooValue = Foo(0, 0)
auto fooContainer = std::vector<Foo>(size, initialFooValue);

int i = 0;
for (auto& moveFoo : whereverYouAreFillingFrom)
{
    fooContainer[i] = std::move(moveFoo);
    ++i;
}

Since std::vector is contiguous you can also just memcopy into it safely since your objects are trivially-copyable.

Upvotes: 1

Related Questions