Search code examples
c++constructorstdstdvectorstdlist

How to manually initialize std::list 's objects in C++? What to do if a pointer of class has a member list<>, can not use new?


Suppose this is a struct/class in c++

struct MyClass{
  list<int> ls;
};

Now maybe due to some reason, it is not possible to initialize pointer of 'MyClass', and the memory is being allocated manually

MyClass *pointer = calloc(1, sizeof(MyClass));
pointer->ls.push_back(123); // <----- this line causes core dump

Although it works fine with pure C++ method like this:-

MyClass *pointer = new MyClass;
pointer->ls.push_back(123);// All good, no problem

So is there any function available in std::list to resolve this issue?

I tried doing the same with std::vector, by there was no problem in that when I used calloc()

struct MyClass{
  vector<int> ls;
};
MyClass *pointer = calloc(1, sizeof(MyClass));//using calloc
pointer->ls.push_back(123); // All Good

But on doing this using malloc:-

struct MyClass{
  vector<int> ls;
};
MyClass *pointer = malloc(1, sizeof(MyClass));//using malloc
pointer->ls.push_back(123); // Core dump :-(

Solution

  • C++ differentiates between memory and objects in that memory. You are allocating memory with malloc or calloc, but you are not creating any object in the memory you allocated.

    You can manually create an object in allocated storage using the placement-new expression:

    new(pointer) MyClass /*initializer*/;
    

    This creates a new MyClass object at the storage location that pointer points to. /*initializer*/ is the optional initializer for the object and has the same meaning as in a variable declaration

    MyClass my_class /*initializer*/;
    

    You may need to #include<new> to use the placement-new form I used above and you need to make sure that the allocated memory block is large enough and sufficiently aligned for the MyClass object type.

    Using a pointer as if it points to an object even though none was created at the storage location causes undefined behavior, which you are observing. Note that this is true for any (even fundamental) types technically. Probably that will change in the future to allow at least trivial types (or some similar category) to be created implicitly when used, but for non-trivial class types such as the standard containers, this certainly will not change.


    Also note that malloc and calloc return void*, which in C++ cannot be implicitly cast to a different pointer type. You need to cast it explicitly:

    MyClass *pointer = static_cast<MyClass*>(malloc(sizeof(MyClass)));
    

    or rather than doing that, save the pointer as void* without any cast to hint at you that it is not actually pointing to an object, but just memory.

    You can still pass that void* pointer to the placement-new and the placement-new will actually return you a pointer of the correct type pointing to the new object (there are some specific cases where you are actually required to use this pointer):

    void *mem_ptr = malloc(sizeof(MyClass));
    auto obj_ptr = new(mem_ptr) MyClass /*initializer*/;
    

    You should also avoid using malloc and calloc and the like. C++ has its own memory allocation function, called (confusingly) operator new. It is used like malloc (initializing the memory to zero like calloc does is not useful, because the initializer in the placement-new can and/or will do that):

    void* pointer = operator new(sizeof(MyClass));
    

    and its free analogue is:

    operator delete(pointer);
    

    It still only allocates memory and does not create object as the placement new does.


    The non-placement new expression new MyClass; allocates memory (by call to operator new) and creates an object as if by the placement-new form mentioned above.


    You do not need to bother with all of this though, because you can just use a smart pointer and initialize it later, if you need that:

    std::unique_ptr<MyClass> ptr; // No memory allocated or object created
    
    ptr = std::make_unique<MyClass>(/*constructor arguments*/); // Allocates memory and creates object
    
    // Object is automatically destroyed and memory freed once no `std::unique_ptr` references the object anymore.
    

    You should not use raw pointers returned from new, malloc or the like as owning pointers in the first place. std::unique_ptr has the correct ownership semantics by default and requires no further actions by you.