5

This subject came up in this thread about a change to std::list::sort() for Visual Studio 2015:

`std::list<>::sort()` - why the sudden switch to top-down strategy?

The new version of std::list::sort does not require a default constructible std::list, as it only uses iterators, and doesn't create any local lists, so it doesn't matter if lists can't be default constructed. The prior version uses local lists (note - each instance of a list involves a dynamic allocation of a sentinel node):

typedef list<_Ty, _Alloc> _Myt;
    // ...
   const size_t _MAXBINS = 25;
   _Myt _Templist, _Binlist[_MAXBINS];

I'm trying to create a non-default constructible list, with Visual Studio 2015 version to test how the change to std::list::sort() handles this.

First I tried the Microsoft C++ 11 minimal allocator example. udpate - I had to change one line in order for Jonathan Wakely's answer to work and demonstrate the issue:

template <class T>  
struct Mallocator  
{  
    typedef T value_type;  
//  Mallocator() noexcept {}  // replaced this line from the Microsoft example
    Mallocator(T) noexcept {} // no default constructor

    // A converting copy constructor:  
    template<class U> Mallocator(const Mallocator<U>&) noexcept {}  
    template<class U> bool operator==(const Mallocator<U>&) const noexcept  
    {  
        return true;  
    }  
    template<class U> bool operator!=(const Mallocator<U>&) const noexcept  
    {  
        return false;  
    }  
    T* allocate(const size_t n) const;  
    void deallocate(T* const p, size_t) const noexcept;  
};  

template <class T>  
T* Mallocator<T>::allocate(const size_t n) const  
{
    if (n == 0)  
    {  
        return nullptr;  
    }  
    if (n > static_cast<size_t>(-1) / sizeof(T))  
    {  
        throw std::bad_array_new_length();  
    }  
    void* const pv = malloc(n * sizeof(T));  
    if (!pv) { throw std::bad_alloc(); }  
    return static_cast<T*>(pv);  
}  

template<class T>  
void Mallocator<T>::deallocate(T * const p, size_t) const noexcept  
{  
    free(p);  
}  

update - with Mallocator changed to have no default constructor, this now results in a compile error:

typedef unsigned long long uint64_t;
    std::list <uint64_t, Mallocator<uint64_t>> dll; // doubly linked list

Using the suggested change from Jonathan Wakely works and reproduces the issue where the old std::list::sort gets a compile error due to local lists and shows that the new std::list::sort with no local lists works with no default constructor:

    std::list<uint64_t, Mallocator<uint64_t>> dll(Mallocator<uint64_t>(0));

I also tried this method based on a thread here at SO:

struct Allocator {
    void construct(void* p, const void* container) const {};
    void destruct(void* p, const void* container) const {};
};

void* operator new (size_t size, const Allocator& alloc, const void* container)
{
    void* allocated_memory = std::malloc(size);
    if (!allocated_memory) {
        throw std::bad_alloc();
    }

    alloc.construct(allocated_memory, container);
    return allocated_memory;
}

void operator delete(void* p, const Allocator& alloc, const void* container)
{
    alloc.destruct(p, container);
    std::free(p);
}

In main

typedef unsigned long long uint64_t;
// ...
    Allocator alloc;
    std::list<uint64_t> *dll = new(alloc, NULL)std::list<uint64_t>;
    // ...
    operator delete(dll, alloc, NULL);

but this works for both the old and new versions of std::list::sort, so it's getting a default constructor.

So the question was how do I create a non default constructible allocator?

Thanks to the demo from Igor Tandetni and answer from Jonathan Wakely, I was able to change the Microsoft example allocator above (noted in the comments) to not have a default constructor, and reproduce the issue related to the old std::list::sort.

Community
  • 1
  • 1
rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • 1
    If your allocator is not default-constructible, then a) you need to provide some other means to construct one, and b) you need to pass an instance, constructed via those means, to `std::list` constructor (all its constructors take an allocator as an optional last parameter). – Igor Tandetnik Nov 18 '16 at 18:25
  • @IgorTandetnik - The issue is how do I create a non default constructible allocator? – rcgldr Nov 18 '16 at 18:28
  • 3
    Don't give it a default constructor, and don't try to default construct it! – Jonathan Wakely Nov 18 '16 at 18:29
  • 1
    [Demo](http://rextester.com/OWXCH65544) – Igor Tandetnik Nov 18 '16 at 18:56
  • @IgorTandetnik - Thanks. Using the demo you provided allowed me to see that compile time failure with the old std::list::sort() (the new one in VS2015 compiled and worked fine). What is the point of the 42 passed as an argument, is that preallocating space for 42 nodes? – rcgldr Nov 18 '16 at 19:29
  • 1
    42, of course, is [The Answer to the Ultimate Question of Life, the Universe, and Everything](https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#Answer_to_the_Ultimate_Question_of_Life.2C_the_Universe.2C_and_Everything_.2842.29). There's no default constructor, only a constructor taking an `int` (and not using it in any way), so the code needs to pass *some* number, doesn't matter which. – Igor Tandetnik Nov 18 '16 at 19:52
  • @IgorTandetnik - in the demo, you can use T instead of int in the line: MyAlloc(T) {} // suppress default constructor. – rcgldr Nov 18 '16 at 20:43
  • 1
    Whatever. It doesn't matter. I just needed some - any - non-default constructor. Feel free to make it whatever you like. – Igor Tandetnik Nov 18 '16 at 20:47
  • @IgorTandetnik - Based on your demo, I was able to also remove the default constructor from the Microsoft example. However your demo is a much better example, as it's "minimal". Thanks for the help. – rcgldr Nov 18 '16 at 21:05

1 Answers1

5

If you default construct a std::list then it will default-construct its allocator, so this variable definition still requires a default constructor:

std::list <uint64_t, Mallocator<uint64_t>> dll; // doubly linked list

If you want to test allocators without default constructors you need to do it differently e.g.

std::list <uint64_t, Mallocator<uint64_t>> dll(Mallocator<uint64_t>(args));

Or:

Mallocator<uint64_t> alloc(some, args, for, your, allocator);
std::list <uint64_t, Mallocator<uint64_t>> dll(alloc);
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 1
    I had to change one line from the Microsoft example (I updated my question) to disable the default constructor. This allowed your method to test allocators without default constructors to work. – rcgldr Nov 18 '16 at 20:10
  • I forgot to ask why anyone would want to create an allocator with no default constructor, at least in the case of std::list? Perhaps this makes more sense for other container / object types or special environments? – rcgldr Nov 21 '16 at 09:56
  • It is just as valid for std::list as anything else. There might be no sensible default property for a custom allocator (e.g. an initial arena size, or a shared memory segment to use, or a log file to write info about allocations to), and so it always requires a constructor argument. If you want a std::list to use that custom allocator then you need to provide the argument. Such things are rarely needed, but that doesn't mean they don't make sense. They generally are only needed for special environments, so you don't need to lose sleep over it. – Jonathan Wakely Nov 21 '16 at 11:37