Everard
Everard

Reputation: 141

Ending lifetime of STL container without calling the destructor

QUESTION

Is it allowed by the C++11 Standard to end lifetime of the container (std::map, for example) without invoking its destructor, if such container does not need to invoke destructors of elements which it contains and memory does not need to be deallocated (with Allocator::deallocate).

IN-DEPTH EXPLANATION

C++11 Standard states the following:

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

This is clear and straightforward. For example, there is some object which allocates memory during its lifetime and releases memory on destruction. If program depends on releasing memory, then not calling destructor of the object leads to undefined behavior. On the other hand, if object obtains memory from some memory pool, then there is no need to call the destructor, because program does not depend on its side effects and behavior is well-defined.

But what about STL containers like std::map, std::list, etc.? Standard states that conforming implementation must follow AS-IF rule. As long as observable behavior is the same, implementation may vary.

I am trying to say that, for example, as stated in table 96 (container requirements), the destructor of container shall invoke destructors of its elements and deallocate all the memory. But what if it also uses some mutexes inside. Using one inside the container is not prohibited by standard (am I wrong?). Not invoking the destructor of mutex might lead to undefined behaviour.

I would like to know, if it is allowed by standard to use std::map and end its lifetime without calling of the destructor. For example, std::map uses custom allocator. This allocator uses some memory pool and to release memory no deallocation function is needed. Since all memory in the container is obtained using this allocator, the program which uses such container does not depend on side effects of destructor.

Code:

class MemoryPool
{
public:
    ...

    // Pre-allocates memory.
    // Returns true on success.
    bool initialize(uint32_t size)
    {
        ...
    }

    // Returns properly aligned block of memory from pre-allocated area.
    template <class T> T* allocate(size_t n = 1)
    {
        ...
    }

    ...
};

template <class T> class CustomAllocator
{
public:
    CustomAllocator(MemoryPool& memoryPool): memoryPool_(&memoryPool) {}

    ...

    /* this allocator obtains memory from memory pool */
    T* allocate(size_t n)
    {
        return memoryPool_->allocate<T>(n);
    }

    // This function may be a no-op, it depends on the implementation of
    // memory pool. It doesn't really matter in context of this question.
    // Anyway, all memory is already allocated in memory pool, so whether there
    // is a need to mark unused chunks or not depends on actual application.
    void deallocate(T*, size_t) {}
    ...

private:
    MemoryPool* memoryPool_;
    ...
};

MemoryPool memoryPool;
memoryPool.initialize();

// I intentionally use only fundamental types in this map
// since not invoking the destructors of such objects
// will not lead to undefined behavior
typedef std::map
<
    uint32_t, uint32_t,
    std::less<uint32_t>,
    CustomAllocator<uint32_t>
> SomeMap;

SomeMap* someMap = memoryPool.allocate<SomeMap>();
new(someMap) SomeMap(CustomAllocator<uint32_t>{memoryPool});

// no destructor of SomeMap is called
// memory is deallocated in destructor of memory pool

Upvotes: 2

Views: 383

Answers (1)

Everard
Everard

Reputation: 141

I've asked this question here: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/R_KPazXbE0k

According to 17.5.2.3 / 1:

Clauses 18 through 30 and Annex D do not specify the representation of classes, and intentionally omit specification of class members (9.2). An implementation may define static or non-static class members, or both, as needed to implement the semantics of the member functions specified in Clauses 18 through 30 and Annex D.

In other words, implementation may have some private members which use resources other than memory. Thus, there is no guarantee that there are no other side effects (at least in the current C++11 Standard).

Upvotes: 1

Related Questions