8

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...

Fabien
  • 12,486
  • 9
  • 44
  • 62
  • 4
    Placement `new` with manual allocation might help. – DCoder May 30 '13 at 14:33
  • 13
    `int data[0]` isn't valid C++. – Kerrek SB May 30 '13 at 14:33
  • 3
    Placement new syntax: `new (address) Type(arguments...);` where `address` is `void*`. Anything created with placement `new` must be destroyed with an explicit destructor call; I somehow doubt that you want to do that manually so you'll best write a wrapper around `Foo` that deals with proper allocation/deallocation. – Matthieu M. May 30 '13 at 14:40
  • Any reason why you can't use `std::vector`? – Philipp May 30 '13 at 14:52
  • @KerrekSB: Sorry to nitpick, but no, that's valid C++; when the size is 0 it'll still return a valid pointer, but dereferencing it is undefined. Details: http://stackoverflow.com/questions/1087042/c-new-int0-will-it-allocate-memory – legends2k May 30 '13 at 14:52
  • @legends2k: That has nothing to do with my comment. – Kerrek SB May 30 '13 at 14:53
  • @Philipp, actually the methods I've hidden do a lot of useful things so I can't use a `vector` instead. The library I'm supposed to use does a lot of micro-optimizations tricks, that's one of them. – Fabien May 30 '13 at 14:54
  • 1
    The draft standard I have has a section [dcl.array] where it clearly states about arrays "If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero." – jcoder May 30 '13 at 14:59
  • That's indeed not valid C++, but it's supported by some compilers such as [GCC](http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html). – zakinster May 30 '13 at 15:02
  • Relevant to the "Is is valid?" discussion: http://stackoverflow.com/questions/4412749/are-flexible-array-members-valid-in-c – Ben Voigt May 30 '13 at 15:03
  • 3
    @KerrekSB: My bad, confused dynamic alloc. with automatic storage :( Just checked the spec. `If the constant-expression is present, it shall be an integral constant expression and its value shall be greater than zero.`; With `-pedantic` g++ flags `warning: ISO C++ forbids zero-size array`. – legends2k May 30 '13 at 15:05

4 Answers4

11

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);
zakinster
  • 10,508
  • 1
  • 41
  • 52
  • 3
    Would be slightly safer to do `malloc(sizeof(Foo) + ...` rather than `sizeof(int)`. Just in case `Foo` gets another member in future. – user9876 May 30 '13 at 14:44
  • 2
    You can actually avoid malloc() and do `void* memory = new char[sizeof(Foo) + sizeof(int) * size];` – Nikos C. May 30 '13 at 14:51
  • `(void*)foo == memory` is guaranteed, and it is a good thing, or else you wouldn't have allocated enough memory. – Ben Voigt May 30 '13 at 15:02
  • @BenVoigt I wasn't sure about that. – zakinster May 30 '13 at 15:11
  • 1
    Don't use `malloc` or `new char[]` if you want raw memory, just use `::operator new`. Its only purpose in life is to allocate raw memory. – GManNickG May 30 '13 at 23:30
  • @GManNickG That's indeed the correct C++ way of doing it, but is there any practical difference between `::operator new` and `malloc` ? – zakinster May 31 '13 at 07:40
  • @zakinster: You can replace the definition of `::operator new` with a custom memory manager. By using `malloc`, you bypass that. – GManNickG May 31 '13 at 15:48
  • After using `::operator new` and placement `new`, wouldn't it be valid to just `delete foo;`? – aschepler May 31 '13 at 17:47
  • 1
    @aschepler: No, you can only `delete` what you `new`'d. While it may share the lexical word "new", they are different things. To undo `::operator new`, you `::operator delete`. – GManNickG Jun 01 '13 at 18:47
  • But you did `new` the `Foo` object, in the expression `new (memory) Foo(size+data)`. (Though it should be `::delete foo;`, not just `delete foo;`.) – aschepler Jun 01 '13 at 19:44
  • @aschepler: That's not `new`ing a `Foo`, that's `new (memory)`ing a `Foo`. :) There is no `delete (memory)` in the language to match which is where the asymmetry kicks in (missing delete). – GManNickG Jun 02 '13 at 21:37
  • Are there no alignment issues with that kind of things? – ABu Sep 15 '16 at 05:09
8

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.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
1

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.

NtscCobalt
  • 1,639
  • 2
  • 15
  • 31
  • That's a solution I thought of, but alas I'm forced to use to provided library for performance reasons... – Fabien May 30 '13 at 14:59
  • 1
    `std::vector`'s own content is contiguous within itself, but it is not contiguous with the larger structure. – Ben Voigt May 30 '13 at 15:08
1

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.

Alexander Oh
  • 24,223
  • 14
  • 73
  • 76