Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory allocation inside constructors?

I am designing an std::vector-like class for self teaching purposes, but I have come upon a difficult issue of memory allocations inside constructors.

The std::vector's capacity constructor is very convenient, but comes at a cost of a potential to throw a std::bad_alloc exception, and take down your whole program with it.

I am struggling to decide what would be the most elegant way to deal with the unlikely scenario of the capacity constructor failing, or best notify the user that by using the constructor, they are consenting to the data structure being able to take down the whole program through an exception.

My first thought was to add a compile-time warning whenever the constructor is called, reminding that the constructor can fail, and that they should make sure to either handle it, or be aware of the risks involved with using the constructor.

This solution seemed bad because it, if applied on global scale, would cause too many warnings, and make a bad impression.

My second idea was to make the constructor private, and require the constructor to be reached through a static "requestConstruct"-like method, similar to Singleton pattern.

This solution makes the interface look odd.

I had a few more ideas, but all of them seem to damage the "feel" of the interface. These ideas are:

  • boost-like boost::optional
  • Haskell-like maybes
  • forcing the user to give me a pool of memory that the structure is authorized to. This idea seems very elegant but I can't find a clean/popular static memory pool implementation for C/C++. I did successfully make a test at https://github.com/CodeDmitry/MemoryPools though.
  • use descriptive assertions apologizing for blowing up the world, and explain what happened in detail. Which is what I am doing at the moment.
  • try to allocate a few more times before calling it quits and crash the whole program. This seems like a good strategy, but feels more like crossing fingers, as the program can still crash due to behavior of the structure(rather than the program). This approach may just be paranoid and wrong since a failed allocation won't necessarily give you the chance to "re-allocate", and will just shut down the program.
  • invent some sort of stress-test mechanism to give confidence to the owner of the structure that it can handle the most capacity the user expects (which can be hard because these stress-tests can be very misleading, as memory can be available now, but under more memory intensive times, it may not).

There is also a funny possibility of not having enough memory left to actually catch the exception. This program seems to not catch it(Which may or may not be related to having enough memory).

#include <stdint.h>
#include <exception>
#include <iostream>
#include <stdlib.h>

int main(int argc, char **argv) 
{
    uint_fast32_t leaked_bytes = 0;

    for (;;) {    
        try {
            new uint8_t;
        } catch (const std::bad_alloc& e) {
            std::cout << "successfully leaked" << leaked_bytes << " bytes." << '\n';
            exit(0);
        } catch (const std::exception& e) {
            std::cout << "I caught an exception, but not sure what it was...\n";
            exit(0);
        }   

        ++leaked_bytes;     
    }
}

This program does let me handle the failure before the program is terminated though:

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    uint_fast32_t bytes_leaked = 0;

    for (;;) {
        if (malloc(1) == 0) {
            printf("leaked %u bytes.\n", bytes_leaked);
            exit(0);
        }        
        ++bytes_leaked;
    }

    return 0;
}

nothrow works correctly as well:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <new>

int main(int argc, char **argv)
{
    uint64_t leaked_bytes = 0;

    for (;;) {         
        uint8_t *byte = new (std::nothrow) uint8_t;
        if (byte == nullptr) {
            printf("leaked %llu bytes.\n", leaked_bytes);
            exit(0);
        }        
        ++leaked_bytes;
    }

    return 0;
}

EDIT:

I think I found a solution to this issue. There is inherent naivety in storing dynamic data structures on the main process. That is, these structures are not guaranteed to succeed, and may break at any point.

It is better to do creation of all dynamic structures in another process, and have some sort of restart policy in the event that it has an unexpected issue.

This does impose overhead for communicating to the data structure, but it stops the main program from having to manage everything that can go wrong with the data structure, which can quickly take over majority of the code in main.

end EDIT.

Is there a proper way to handle allocation issues in such dynamic classes, increase awareness of my user about these risks?

like image 602
Dmitry Avatar asked Apr 08 '16 06:04

Dmitry


People also ask

When memory is allocated to a constructor in C++?

When new is used to allocate memory for a C++ class object, the object's constructor is called after the memory is allocated. Use the delete operator to deallocate the memory allocated by the new operator.

Is constructor dynamically allocated?

When allocation of memory is done dynamically using dynamic memory allocator new in a constructor, it is known as dynamic constructor. By using this, we can dynamically initialize the objects.

What are the different types of memory allocations?

There are two types of memory allocations. Static and dynamic.

Is destructors are used to allocate memory?

It is automatically called when object goes out of scope. Destructor release memory space occupied by the objects created by constructor. In destructor, objects are destroyed in the reverse of an object creation.


1 Answers

Based on the feedback provided, I am convinced that there is no elegant way to prevent classes that manage dynamic memory from taking down your program without introducing complexity.

Throwing exceptions is questionable because once your class has no memory it can allocate, it may not be able to handle std::bad_alloc that a user can catch.

From more thinking, I come to a realization that one way to prevent the program from crashing from modules it uses, is to move the parts of the program that use these modules to another process, and have that process share memory with the main process, so that if the other process somehow goes down, you can just restart that process and make another request.

As long as you use any classes capable of failing under extreme cases, it is impossible to prevent them from failing unless you are able to control their extreme cases.

In the case of dynamic memory, it is difficult to control exactly how dynamic memory your process has access to, what the allocation policy it has, and how much memory in total has been used by your application. It is thus difficult to control it without being more rigorous about the pools of contiguous memory you use, or moving the data structure to be managed by another process which can be restarted on failure.

Answer: It is inherently impossible to both provide a simple interface/implementation pair of a class that manages dynamic memory. You either have a simple interface/implementation that will take down your program such as std::vector; or a complex interface/implementation that requires one of the following or something more clever:

  1. Programmer must provide blocks of memory that are already available. Since memory is already there and you know how much of it you have, you can just not allocate more than you can afford.
  2. The data structure uses its own memory management scheme using a separate process that will crash instead of the process the structure is hosted on.
  3. The data structure is managed by a separate process altogether, similar to database servers. This makes it easy to restart the process in case they fail.
like image 177
Dmitry Avatar answered Oct 11 '22 04:10

Dmitry