Home > Enterprise >  What is "uses allocator" and "scoped allocator" construction in c
What is "uses allocator" and "scoped allocator" construction in c

Time:05-20

There are quite a few questions on here which talk about uses allocator and scoped allocator but they come from an assumption of knowing what these are and what they are for.

The explanation in the usual C references explains the what but not so much the how or why.

This site is missing a canonical answer that will provide the background for those unfamiliar with the topic.

CodePudding user response:

It is a protocol that allows generic container types (in the broader sense of the word) to detect when generic type parameters expect to be using with allocators.

They can then decide to support that by automatically propagating their outside allocators to the inner types. The quintessential example would be when you want to have a std::map<std::vector<std::string>, std::set<int> > all using the same allocator family.

Types containing a nested allocator_type typename will automatically be detected as uses_allocator types, which includes all standard containers.

Typical other uses_allocator types in the standard library are std::pair and std::tuple, but also function wrapper types (std::function, std::packaged_type) and container adaptors (std::stack and friends).

Scoped allocators allow adapting an outer allocator or an outer allocator and set of inner allocators if they are not identical, informing a scoped-allocator-aware container of the allocator types to be used for inner "uses-allocator" types.

From the boost documentation for scoped_allocator_adaptor:

This class is a C 03-compatible implementation of std::scoped_allocator_adaptor. The class template scoped_allocator_adaptor is an allocator template that specifies the memory resource (the outer allocator) to be used by a container (as any other allocator does) and also specifies an inner allocator resource to be passed to the constructor of every element within the container.

This adaptor is instantiated with one outer and zero or more inner allocator types. If instantiated with only one allocator type, the inner allocator becomes the scoped_allocator_adaptor itself, thus using the same allocator resource for the container and every element within the container and, if the elements themselves are containers, each of their elements recursively.

If instantiated with more than one allocator, the first allocator is the outer allocator for use by the container, the second allocator is passed to the constructors of the container's elements, and, if the elements themselves are containers, the third allocator is passed to the elements' elements, and so on. If containers are nested to a depth greater than the number of allocators, the last allocator is used repeatedly, as in the single-allocator case, for any remaining recursions.

In my experience, Boost Container has always supported this protocol better than most standard library implementations, but I admit I haven't checked up on the state of those in years.

I've just provided you with an example of using scoped_allocator_adaptor in this answer: Does boost interprocess support sharing objects containing pointers between processes?. In that picture, Shared::Bar is an example of an inner "uses-allocator" type, and it informs the container (vector) to pass a (converted) copy of its allocator along when constructing elements.

I replicate that answer's code listing here, to make sure the example reference doesn't get out of date:

Live On Coliru

#include <boost/container/scoped_allocator.hpp>
#include <boost/container/string.hpp>
#include <boost/container/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <string>
namespace bip = boost::interprocess;

namespace Shared {
#ifdef COLIRU
    using Segment = bip::managed_mapped_file;
#else
    using Segment = bip::managed_shared_memory;
#endif
    using Mgr     = Segment::segment_manager;

    template <typename T>
    using Alloc = boost::container::scoped_allocator_adaptor< //
        bip::allocator<T, Mgr>>;

    template <typename T> using Vector = std::vector<T, Alloc<T>>;

    using String = boost::container::basic_string< //
        char, std::char_traits<char>, Alloc<char>>;

    struct Bar {
        using allocator_type = Alloc<char>;
        String first_name, last_name;

        template <typename Alloc>
        Bar(std::allocator_arg_t, Alloc alloc) : first_name(alloc)
                                               , last_name(alloc) {}

        template <typename Alloc>
        Bar(Alloc&& alloc) : first_name(alloc)
                           , last_name(alloc) {}

        Bar(Bar const&) = default;
        template <typename Alloc>
        Bar(Bar const& rhs, Alloc alloc) : first_name(rhs.first_name, alloc), last_name(rhs.last_name, alloc) {}

        Bar& operator=(Bar const&) = default;

        template <typename Alloc> Bar(std::string_view first_name, std::string_view last_name, Alloc alloc) : 
            first_name(first_name, alloc), last_name(last_name, alloc) {}
    };

    struct Snafu {
        std::array<int, 5> no_problem;
    };

    struct Foo {
        Vector<Bar>   bars;
        Vector<Snafu> snafus;

        template <typename Alloc> //
        Foo(Alloc alloc) : bars(alloc)
                         , snafus(alloc) {}
    };
}

#include <iostream>
static inline std::ostream& operator<<(std::ostream& os, Shared::Foo const& foo) {
    os << "Foo\n================\nBars:\n";
    for (auto& [f, l] : foo.bars)
        os << " - " << f << ", " << l << "\n";

    os << "Snafus:";
    for (auto& s : foo.snafus) {
        os << "\n - ";
        for (auto el : s.no_problem)
            os << " " << el;
    }
    return os << "\n";
}


int main() { Shared::Segment msm(bip::open_or_create, "my_shared_mem", 10ull << 10);
    Shared::Foo& foo = *msm.find_or_construct<Shared::Foo>("the_foo") //
                        (msm.get_segment_manager()); // constructor arguments

    foo.bars.emplace_back("John", "Doe");
    foo.bars.emplace_back("Jane", "Deer");
    foo.bars.emplace_back("Igor", "Stravinsky");
    foo.bars.emplace_back("Rimsky", "Korsakov");

    foo.snafus.push_back({1, 2, 3});
    foo.snafus.push_back({2, 3, 4});
    foo.snafus.push_back({3, 4, 5, 6, 7});

    std::cout << foo << "\n";
}
  • Related