Custom allocators as alternatives to vector of smart pointers?





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{
margin-bottom:0;
}








14















This question is about owning pointers, consuming pointers, smart pointers, vectors, and allocators.



I am a little bit lost on my thoughts about code architecture. Furthermore, if this question has already an answer somewhere, 1. sorry, but I haven't found a satisfying answer so far and 2. please point me to it.



My problem is the following:



I have several "things" stored in a vector and several "consumers" of those "things". So, my first try was like follows:



std::vector<thing> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return &i_am_the_owner_of_things[5]; // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


In my application, this would be safe because the "things" outlive the "consumers" in any case. However, more "things" can be added during runtime and that can become a problem because if the std::vector<thing> i_am_the_owner_of_things; gets reallocated, all the thing* m_thing pointers become invalid.



A fix to this scenario would be to store unique pointers to "things" instead of "things" directly, i.e. like follows:



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return i_am_the_owner_of_things[5].get(); // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


The downside here is that memory coherency between "things" is lost. Can this memory coherency be re-established by using custom allocators somehow? I am thinking of something like an allocator which would always allocate memory for, e.g., 10 elements at a time and whenever required, adds more 10-elements-sized chunks of memory.



Example:

initially:

v = ☐☐☐☐☐☐☐☐☐☐

more elements:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐

and again:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐



Using such an allocator, I wouldn't even have to use std::unique_ptrs of "things" because at std::vector's reallocation time, the memory addresses of the already existing elements would not change.



As alternative, I can only think of referencing the "thing" in "consumer" via a std::shared_ptr<thing> m_thing, as opposed to the current thing* m_thing but that seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.



So, is the allocator-approach a good one? And if so, how can it be done? Do I have to implement the allocator by myself or is there an existing one?










share|improve this question






















  • 1





    Do multiple consumer use the same thing? Because if not, wouldn't it be more appropriate to move the ownership from the vector to the consumer?

    – Mike van Dyke
    May 27 at 10:43











  • do you know maximum number of things ahead? If yes the call reserve on a vector and there will not be a reallocation of elements.

    – Marek R
    May 27 at 10:43













  • Yes, multiple consumers can use the same thing. That's the point, the ownership shall not be moved to the consumer.

    – j00hi
    May 27 at 10:44






  • 1





    @MarekR Yes, that would maybe be an option. But it can never be a clean solution because on one hand, you want this upper bound to be as tight as possible. And what if you, in some rare situation, need more?

    – j00hi
    May 27 at 10:46






  • 1





    "The downside here is that memory coherency between "things" is lost." - why is it important?

    – Igor G
    May 27 at 11:20


















14















This question is about owning pointers, consuming pointers, smart pointers, vectors, and allocators.



I am a little bit lost on my thoughts about code architecture. Furthermore, if this question has already an answer somewhere, 1. sorry, but I haven't found a satisfying answer so far and 2. please point me to it.



My problem is the following:



I have several "things" stored in a vector and several "consumers" of those "things". So, my first try was like follows:



std::vector<thing> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return &i_am_the_owner_of_things[5]; // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


In my application, this would be safe because the "things" outlive the "consumers" in any case. However, more "things" can be added during runtime and that can become a problem because if the std::vector<thing> i_am_the_owner_of_things; gets reallocated, all the thing* m_thing pointers become invalid.



A fix to this scenario would be to store unique pointers to "things" instead of "things" directly, i.e. like follows:



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return i_am_the_owner_of_things[5].get(); // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


The downside here is that memory coherency between "things" is lost. Can this memory coherency be re-established by using custom allocators somehow? I am thinking of something like an allocator which would always allocate memory for, e.g., 10 elements at a time and whenever required, adds more 10-elements-sized chunks of memory.



Example:

initially:

v = ☐☐☐☐☐☐☐☐☐☐

more elements:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐

and again:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐



Using such an allocator, I wouldn't even have to use std::unique_ptrs of "things" because at std::vector's reallocation time, the memory addresses of the already existing elements would not change.



As alternative, I can only think of referencing the "thing" in "consumer" via a std::shared_ptr<thing> m_thing, as opposed to the current thing* m_thing but that seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.



So, is the allocator-approach a good one? And if so, how can it be done? Do I have to implement the allocator by myself or is there an existing one?










share|improve this question






















  • 1





    Do multiple consumer use the same thing? Because if not, wouldn't it be more appropriate to move the ownership from the vector to the consumer?

    – Mike van Dyke
    May 27 at 10:43











  • do you know maximum number of things ahead? If yes the call reserve on a vector and there will not be a reallocation of elements.

    – Marek R
    May 27 at 10:43













  • Yes, multiple consumers can use the same thing. That's the point, the ownership shall not be moved to the consumer.

    – j00hi
    May 27 at 10:44






  • 1





    @MarekR Yes, that would maybe be an option. But it can never be a clean solution because on one hand, you want this upper bound to be as tight as possible. And what if you, in some rare situation, need more?

    – j00hi
    May 27 at 10:46






  • 1





    "The downside here is that memory coherency between "things" is lost." - why is it important?

    – Igor G
    May 27 at 11:20














14












14








14


3






This question is about owning pointers, consuming pointers, smart pointers, vectors, and allocators.



I am a little bit lost on my thoughts about code architecture. Furthermore, if this question has already an answer somewhere, 1. sorry, but I haven't found a satisfying answer so far and 2. please point me to it.



My problem is the following:



I have several "things" stored in a vector and several "consumers" of those "things". So, my first try was like follows:



std::vector<thing> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return &i_am_the_owner_of_things[5]; // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


In my application, this would be safe because the "things" outlive the "consumers" in any case. However, more "things" can be added during runtime and that can become a problem because if the std::vector<thing> i_am_the_owner_of_things; gets reallocated, all the thing* m_thing pointers become invalid.



A fix to this scenario would be to store unique pointers to "things" instead of "things" directly, i.e. like follows:



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return i_am_the_owner_of_things[5].get(); // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


The downside here is that memory coherency between "things" is lost. Can this memory coherency be re-established by using custom allocators somehow? I am thinking of something like an allocator which would always allocate memory for, e.g., 10 elements at a time and whenever required, adds more 10-elements-sized chunks of memory.



Example:

initially:

v = ☐☐☐☐☐☐☐☐☐☐

more elements:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐

and again:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐



Using such an allocator, I wouldn't even have to use std::unique_ptrs of "things" because at std::vector's reallocation time, the memory addresses of the already existing elements would not change.



As alternative, I can only think of referencing the "thing" in "consumer" via a std::shared_ptr<thing> m_thing, as opposed to the current thing* m_thing but that seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.



So, is the allocator-approach a good one? And if so, how can it be done? Do I have to implement the allocator by myself or is there an existing one?










share|improve this question
















This question is about owning pointers, consuming pointers, smart pointers, vectors, and allocators.



I am a little bit lost on my thoughts about code architecture. Furthermore, if this question has already an answer somewhere, 1. sorry, but I haven't found a satisfying answer so far and 2. please point me to it.



My problem is the following:



I have several "things" stored in a vector and several "consumers" of those "things". So, my first try was like follows:



std::vector<thing> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return &i_am_the_owner_of_things[5]; // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


In my application, this would be safe because the "things" outlive the "consumers" in any case. However, more "things" can be added during runtime and that can become a problem because if the std::vector<thing> i_am_the_owner_of_things; gets reallocated, all the thing* m_thing pointers become invalid.



A fix to this scenario would be to store unique pointers to "things" instead of "things" directly, i.e. like follows:



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing* get_thing_for_consumer() {
// some thing-selection logic
return i_am_the_owner_of_things[5].get(); // 5 is just an example
}

...

// somewhere else in the code:
class consumer {
consumer() {
m_thing = get_thing_for_consumer();
}

thing* m_thing;
};


The downside here is that memory coherency between "things" is lost. Can this memory coherency be re-established by using custom allocators somehow? I am thinking of something like an allocator which would always allocate memory for, e.g., 10 elements at a time and whenever required, adds more 10-elements-sized chunks of memory.



Example:

initially:

v = ☐☐☐☐☐☐☐☐☐☐

more elements:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐

and again:

v = ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐ 🡒 ☐☐☐☐☐☐☐☐☐☐



Using such an allocator, I wouldn't even have to use std::unique_ptrs of "things" because at std::vector's reallocation time, the memory addresses of the already existing elements would not change.



As alternative, I can only think of referencing the "thing" in "consumer" via a std::shared_ptr<thing> m_thing, as opposed to the current thing* m_thing but that seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.



So, is the allocator-approach a good one? And if so, how can it be done? Do I have to implement the allocator by myself or is there an existing one?







c++ c++11 shared-ptr unique-ptr allocator






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited May 27 at 10:43







j00hi

















asked May 27 at 10:36









j00hij00hi

2,3073 gold badges28 silver badges59 bronze badges




2,3073 gold badges28 silver badges59 bronze badges











  • 1





    Do multiple consumer use the same thing? Because if not, wouldn't it be more appropriate to move the ownership from the vector to the consumer?

    – Mike van Dyke
    May 27 at 10:43











  • do you know maximum number of things ahead? If yes the call reserve on a vector and there will not be a reallocation of elements.

    – Marek R
    May 27 at 10:43













  • Yes, multiple consumers can use the same thing. That's the point, the ownership shall not be moved to the consumer.

    – j00hi
    May 27 at 10:44






  • 1





    @MarekR Yes, that would maybe be an option. But it can never be a clean solution because on one hand, you want this upper bound to be as tight as possible. And what if you, in some rare situation, need more?

    – j00hi
    May 27 at 10:46






  • 1





    "The downside here is that memory coherency between "things" is lost." - why is it important?

    – Igor G
    May 27 at 11:20














  • 1





    Do multiple consumer use the same thing? Because if not, wouldn't it be more appropriate to move the ownership from the vector to the consumer?

    – Mike van Dyke
    May 27 at 10:43











  • do you know maximum number of things ahead? If yes the call reserve on a vector and there will not be a reallocation of elements.

    – Marek R
    May 27 at 10:43













  • Yes, multiple consumers can use the same thing. That's the point, the ownership shall not be moved to the consumer.

    – j00hi
    May 27 at 10:44






  • 1





    @MarekR Yes, that would maybe be an option. But it can never be a clean solution because on one hand, you want this upper bound to be as tight as possible. And what if you, in some rare situation, need more?

    – j00hi
    May 27 at 10:46






  • 1





    "The downside here is that memory coherency between "things" is lost." - why is it important?

    – Igor G
    May 27 at 11:20








1




1





Do multiple consumer use the same thing? Because if not, wouldn't it be more appropriate to move the ownership from the vector to the consumer?

– Mike van Dyke
May 27 at 10:43





Do multiple consumer use the same thing? Because if not, wouldn't it be more appropriate to move the ownership from the vector to the consumer?

– Mike van Dyke
May 27 at 10:43













do you know maximum number of things ahead? If yes the call reserve on a vector and there will not be a reallocation of elements.

– Marek R
May 27 at 10:43







do you know maximum number of things ahead? If yes the call reserve on a vector and there will not be a reallocation of elements.

– Marek R
May 27 at 10:43















Yes, multiple consumers can use the same thing. That's the point, the ownership shall not be moved to the consumer.

– j00hi
May 27 at 10:44





Yes, multiple consumers can use the same thing. That's the point, the ownership shall not be moved to the consumer.

– j00hi
May 27 at 10:44




1




1





@MarekR Yes, that would maybe be an option. But it can never be a clean solution because on one hand, you want this upper bound to be as tight as possible. And what if you, in some rare situation, need more?

– j00hi
May 27 at 10:46





@MarekR Yes, that would maybe be an option. But it can never be a clean solution because on one hand, you want this upper bound to be as tight as possible. And what if you, in some rare situation, need more?

– j00hi
May 27 at 10:46




1




1





"The downside here is that memory coherency between "things" is lost." - why is it important?

– Igor G
May 27 at 11:20





"The downside here is that memory coherency between "things" is lost." - why is it important?

– Igor G
May 27 at 11:20












4 Answers
4






active

oldest

votes


















12
















If you are able to treat thing as a value type, do so. It simplifies things, you don't need a smart pointer for circumventing the pointer/reference invalidation issue. The latter can be tackled differently:




  • If new thing instances are inserted via push_front and push_back during the program, use std::deque instead of std::vector. Then, no pointers or references to elements in this container are invalidated (iterators are invalidated, though - thanks to @odyss-jii for pointing that out). If you fear that you heavily rely on the performance benefit of the completely contiguous memory layout of std::vector: create a benchmark and profile.

  • If new thing instances are inserted in the middle of the container during the program, consider using std::list. No pointers/iterators/references are invalidated when inserting or removing container elements. Iteration over a std::list is much slower than a std::vector, but make sure this is an actual issue in your scenario before worrying too much about that.






share|improve this answer




























  • These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

    – j00hi
    May 27 at 10:51






  • 1





    It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

    – lubgr
    May 27 at 10:54








  • 2





    @j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

    – darune
    May 27 at 10:55













  • That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

    – j00hi
    May 27 at 11:03






  • 2





    Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

    – odyss-jii
    May 27 at 12:36





















1
















There is no single right answer to this question, since it depends a lot on the exact access patterns and desired performance characteristics.



Having said that, here is my recommendation:



Continue storing the data contiguously as you are, but do not store aliasing pointers to that data. Instead, consider a safer alternative (this is a proven method) where you fetch the pointer based on an ID right before using it -- as a side-note, in a multi-threaded application you can lock attempts to resize the underlying store whilst such a weak reference lives.



So your consumer will store an ID, and will fetch a pointer to the data from the "store" on demand. This also gives you control over all "fetches", so that you can track them, implement safety measure, etc.



void consumer::foo() {
thing *t = m_thing_store.get(m_thing_id);
if (t) {
// do something with t
}
}


Or more advanced alternative to help with synchronization in multi-threaded scenario:



void consumer::foo() {
reference<thing> t = m_thing_store.get(m_thing_id);
if (!t.empty()) {
// do something with t
}
}


Where reference would be some thread-safe RAII "weak pointer".



There are multiple ways of implementing this. You can either use an open-addressing hash table and use the ID as a key; this will give you roughly O(1) access time if you balance it properly.



Another alternative (best-case O(1), worst-case O(N)) is to use a "reference" structure, with a 32-bit ID and a 32-bit index (so same size as 64-bit pointer) -- the index serves as a sort-of cache. When you fetch, you first try the index, if the element in the index has the expected ID you are done. Otherwise, you get a "cache miss" and you do a linear scan of the store to find the element based on ID, and then you store the last-known index value in your reference.






share|improve this answer




























  • Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

    – Igor G
    May 27 at 11:15











  • @IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

    – odyss-jii
    May 27 at 11:21











  • I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

    – j00hi
    May 27 at 16:53



















-1
















IMO best approach would be create new container which will behave is safe way.



Pros:




  • change will be done on separate level of abstraction

  • changes to old code will be minimal (just replace std::vector with new container).

  • it will be "clean code" way to do it


Cons:




  • it may look like there is a bit more work to do


Other answer proposes use of std::list which will do the job, but with larger number of allocation and slower random access. So IMO it is better to compose own container from couple of std::vectors.



So it may start look more or less like this (minimum example):



template<typename T>
class cluster_vector
{
public:
static const constexpr cluster_size = 16;

cluster_vector() {
clusters.reserve(1024);
add_cluster();
}

...

size_t size() const {
if (clusters.empty()) return 0;
return (clusters.size() - 1) * cluster_size + clusters.back().size();
}

T& operator(size_t index) {
thowIfIndexToBig(index);
return clusters[index / cluster_size][index % cluster_size];
}

void push_back(T&& x) {
if_last_is_full_add_cluster();
clusters.back().push_back(std::forward<T>(x));
}

private:
void thowIfIndexToBig(size_t index) const {
if (index >= size()) {
throw std::out_of_range("cluster_vector out of range");
}
}

void add_cluster() {
clusters.push_back({});
clusters.back().reserve(cluster_size);
}

void if_last_is_full_add_cluster() {
if (clusters.back().size() == cluster_size) {
add_cluster();
}
}

private:
std::vector<std::vector<T>> clusters;
}


This way you will provide container which will not reallocate items. It doesn't meter what T does.






share|improve this answer























  • 3





    Downvote: suggesting to "roll your own" (when standard solutions exist)

    – darune
    May 27 at 11:38













  • you mean std::list? It is not like std::list.

    – Marek R
    May 27 at 11:41



















-2

















[A shared pointer] seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.




So what? Maybe the code is a little less self-documenting, but it will solve all your problems.
(And by the way you are muddling things by using the word "consumer", which in a traditional producer/consumer paradigm would take ownership.)



Also, returning a raw pointer in your current code is already entirely ambiguous as to ownership. In general, I'd say it's good practice to avoid raw pointers if you can (like you don't need to call delete.) I would return a reference if you go with unique_ptr



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing& get_thing_for_consumer() {
// some thing-selection logic
return *i_am_the_owner_of_things[5]; // 5 is just an example
}





share|improve this answer


























  • No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

    – j00hi
    May 28 at 13:01











  • "avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

    – formerlyknownas_463035818
    May 28 at 18:07













Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/4.0/"u003ecc by-sa 4.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});















draft saved

draft discarded
















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56324397%2fcustom-allocators-as-alternatives-to-vector-of-smart-pointers%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























4 Answers
4






active

oldest

votes








4 Answers
4






active

oldest

votes









active

oldest

votes






active

oldest

votes









12
















If you are able to treat thing as a value type, do so. It simplifies things, you don't need a smart pointer for circumventing the pointer/reference invalidation issue. The latter can be tackled differently:




  • If new thing instances are inserted via push_front and push_back during the program, use std::deque instead of std::vector. Then, no pointers or references to elements in this container are invalidated (iterators are invalidated, though - thanks to @odyss-jii for pointing that out). If you fear that you heavily rely on the performance benefit of the completely contiguous memory layout of std::vector: create a benchmark and profile.

  • If new thing instances are inserted in the middle of the container during the program, consider using std::list. No pointers/iterators/references are invalidated when inserting or removing container elements. Iteration over a std::list is much slower than a std::vector, but make sure this is an actual issue in your scenario before worrying too much about that.






share|improve this answer




























  • These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

    – j00hi
    May 27 at 10:51






  • 1





    It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

    – lubgr
    May 27 at 10:54








  • 2





    @j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

    – darune
    May 27 at 10:55













  • That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

    – j00hi
    May 27 at 11:03






  • 2





    Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

    – odyss-jii
    May 27 at 12:36


















12
















If you are able to treat thing as a value type, do so. It simplifies things, you don't need a smart pointer for circumventing the pointer/reference invalidation issue. The latter can be tackled differently:




  • If new thing instances are inserted via push_front and push_back during the program, use std::deque instead of std::vector. Then, no pointers or references to elements in this container are invalidated (iterators are invalidated, though - thanks to @odyss-jii for pointing that out). If you fear that you heavily rely on the performance benefit of the completely contiguous memory layout of std::vector: create a benchmark and profile.

  • If new thing instances are inserted in the middle of the container during the program, consider using std::list. No pointers/iterators/references are invalidated when inserting or removing container elements. Iteration over a std::list is much slower than a std::vector, but make sure this is an actual issue in your scenario before worrying too much about that.






share|improve this answer




























  • These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

    – j00hi
    May 27 at 10:51






  • 1





    It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

    – lubgr
    May 27 at 10:54








  • 2





    @j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

    – darune
    May 27 at 10:55













  • That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

    – j00hi
    May 27 at 11:03






  • 2





    Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

    – odyss-jii
    May 27 at 12:36
















12














12










12









If you are able to treat thing as a value type, do so. It simplifies things, you don't need a smart pointer for circumventing the pointer/reference invalidation issue. The latter can be tackled differently:




  • If new thing instances are inserted via push_front and push_back during the program, use std::deque instead of std::vector. Then, no pointers or references to elements in this container are invalidated (iterators are invalidated, though - thanks to @odyss-jii for pointing that out). If you fear that you heavily rely on the performance benefit of the completely contiguous memory layout of std::vector: create a benchmark and profile.

  • If new thing instances are inserted in the middle of the container during the program, consider using std::list. No pointers/iterators/references are invalidated when inserting or removing container elements. Iteration over a std::list is much slower than a std::vector, but make sure this is an actual issue in your scenario before worrying too much about that.






share|improve this answer















If you are able to treat thing as a value type, do so. It simplifies things, you don't need a smart pointer for circumventing the pointer/reference invalidation issue. The latter can be tackled differently:




  • If new thing instances are inserted via push_front and push_back during the program, use std::deque instead of std::vector. Then, no pointers or references to elements in this container are invalidated (iterators are invalidated, though - thanks to @odyss-jii for pointing that out). If you fear that you heavily rely on the performance benefit of the completely contiguous memory layout of std::vector: create a benchmark and profile.

  • If new thing instances are inserted in the middle of the container during the program, consider using std::list. No pointers/iterators/references are invalidated when inserting or removing container elements. Iteration over a std::list is much slower than a std::vector, but make sure this is an actual issue in your scenario before worrying too much about that.







share|improve this answer














share|improve this answer



share|improve this answer








edited May 27 at 12:39

























answered May 27 at 10:44









lubgrlubgr

26.7k3 gold badges38 silver badges82 bronze badges




26.7k3 gold badges38 silver badges82 bronze badges
















  • These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

    – j00hi
    May 27 at 10:51






  • 1





    It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

    – lubgr
    May 27 at 10:54








  • 2





    @j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

    – darune
    May 27 at 10:55













  • That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

    – j00hi
    May 27 at 11:03






  • 2





    Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

    – odyss-jii
    May 27 at 12:36





















  • These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

    – j00hi
    May 27 at 10:51






  • 1





    It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

    – lubgr
    May 27 at 10:54








  • 2





    @j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

    – darune
    May 27 at 10:55













  • That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

    – j00hi
    May 27 at 11:03






  • 2





    Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

    – odyss-jii
    May 27 at 12:36



















These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

– j00hi
May 27 at 10:51





These are some good points to consider, thanks! How is memory managed in a std::deque? Isn't it also some kind of linked list or does it store memory contiguously?

– j00hi
May 27 at 10:51




1




1





It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

– lubgr
May 27 at 10:54







It stores memory in contiguous chunks. That makes it kind of a hybrid between std::list and std::vector. Have a look at this thread for more info on std::deque.

– lubgr
May 27 at 10:54






2




2





@j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

– darune
May 27 at 10:55







@j00hi Its both, it uses a sort of linked lists of blocks type of layout. The downside is that on most implementations the blocksize is really small and none-growing - the small size can usually be alleviated to some extent by a macro define - but again should be as a result of profiling and not speculation.

– darune
May 27 at 10:55















That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

– j00hi
May 27 at 11:03





That behavior of std::deque is actually exactly what I was looking for when I was asking about custom allocators in my question. I'll leave the question open for just a bit longer, hoping that someone can point me to some more information about custom allocators for this case, because I've asked for these in my question title. Otherwise, thanks a lot. The information in this answer and its comments is very concise and helpful.

– j00hi
May 27 at 11:03




2




2





Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

– odyss-jii
May 27 at 12:36







Nitpick, but is it not so that iterators are in fact invalidated on std::deque::push_back and std::deque::push_front, but not references to the actual elements? Worth mentioning, so that someone does not store an iterator expecting it to remain valid after insertion at the back.

– odyss-jii
May 27 at 12:36















1
















There is no single right answer to this question, since it depends a lot on the exact access patterns and desired performance characteristics.



Having said that, here is my recommendation:



Continue storing the data contiguously as you are, but do not store aliasing pointers to that data. Instead, consider a safer alternative (this is a proven method) where you fetch the pointer based on an ID right before using it -- as a side-note, in a multi-threaded application you can lock attempts to resize the underlying store whilst such a weak reference lives.



So your consumer will store an ID, and will fetch a pointer to the data from the "store" on demand. This also gives you control over all "fetches", so that you can track them, implement safety measure, etc.



void consumer::foo() {
thing *t = m_thing_store.get(m_thing_id);
if (t) {
// do something with t
}
}


Or more advanced alternative to help with synchronization in multi-threaded scenario:



void consumer::foo() {
reference<thing> t = m_thing_store.get(m_thing_id);
if (!t.empty()) {
// do something with t
}
}


Where reference would be some thread-safe RAII "weak pointer".



There are multiple ways of implementing this. You can either use an open-addressing hash table and use the ID as a key; this will give you roughly O(1) access time if you balance it properly.



Another alternative (best-case O(1), worst-case O(N)) is to use a "reference" structure, with a 32-bit ID and a 32-bit index (so same size as 64-bit pointer) -- the index serves as a sort-of cache. When you fetch, you first try the index, if the element in the index has the expected ID you are done. Otherwise, you get a "cache miss" and you do a linear scan of the store to find the element based on ID, and then you store the last-known index value in your reference.






share|improve this answer




























  • Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

    – Igor G
    May 27 at 11:15











  • @IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

    – odyss-jii
    May 27 at 11:21











  • I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

    – j00hi
    May 27 at 16:53
















1
















There is no single right answer to this question, since it depends a lot on the exact access patterns and desired performance characteristics.



Having said that, here is my recommendation:



Continue storing the data contiguously as you are, but do not store aliasing pointers to that data. Instead, consider a safer alternative (this is a proven method) where you fetch the pointer based on an ID right before using it -- as a side-note, in a multi-threaded application you can lock attempts to resize the underlying store whilst such a weak reference lives.



So your consumer will store an ID, and will fetch a pointer to the data from the "store" on demand. This also gives you control over all "fetches", so that you can track them, implement safety measure, etc.



void consumer::foo() {
thing *t = m_thing_store.get(m_thing_id);
if (t) {
// do something with t
}
}


Or more advanced alternative to help with synchronization in multi-threaded scenario:



void consumer::foo() {
reference<thing> t = m_thing_store.get(m_thing_id);
if (!t.empty()) {
// do something with t
}
}


Where reference would be some thread-safe RAII "weak pointer".



There are multiple ways of implementing this. You can either use an open-addressing hash table and use the ID as a key; this will give you roughly O(1) access time if you balance it properly.



Another alternative (best-case O(1), worst-case O(N)) is to use a "reference" structure, with a 32-bit ID and a 32-bit index (so same size as 64-bit pointer) -- the index serves as a sort-of cache. When you fetch, you first try the index, if the element in the index has the expected ID you are done. Otherwise, you get a "cache miss" and you do a linear scan of the store to find the element based on ID, and then you store the last-known index value in your reference.






share|improve this answer




























  • Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

    – Igor G
    May 27 at 11:15











  • @IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

    – odyss-jii
    May 27 at 11:21











  • I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

    – j00hi
    May 27 at 16:53














1














1










1









There is no single right answer to this question, since it depends a lot on the exact access patterns and desired performance characteristics.



Having said that, here is my recommendation:



Continue storing the data contiguously as you are, but do not store aliasing pointers to that data. Instead, consider a safer alternative (this is a proven method) where you fetch the pointer based on an ID right before using it -- as a side-note, in a multi-threaded application you can lock attempts to resize the underlying store whilst such a weak reference lives.



So your consumer will store an ID, and will fetch a pointer to the data from the "store" on demand. This also gives you control over all "fetches", so that you can track them, implement safety measure, etc.



void consumer::foo() {
thing *t = m_thing_store.get(m_thing_id);
if (t) {
// do something with t
}
}


Or more advanced alternative to help with synchronization in multi-threaded scenario:



void consumer::foo() {
reference<thing> t = m_thing_store.get(m_thing_id);
if (!t.empty()) {
// do something with t
}
}


Where reference would be some thread-safe RAII "weak pointer".



There are multiple ways of implementing this. You can either use an open-addressing hash table and use the ID as a key; this will give you roughly O(1) access time if you balance it properly.



Another alternative (best-case O(1), worst-case O(N)) is to use a "reference" structure, with a 32-bit ID and a 32-bit index (so same size as 64-bit pointer) -- the index serves as a sort-of cache. When you fetch, you first try the index, if the element in the index has the expected ID you are done. Otherwise, you get a "cache miss" and you do a linear scan of the store to find the element based on ID, and then you store the last-known index value in your reference.






share|improve this answer















There is no single right answer to this question, since it depends a lot on the exact access patterns and desired performance characteristics.



Having said that, here is my recommendation:



Continue storing the data contiguously as you are, but do not store aliasing pointers to that data. Instead, consider a safer alternative (this is a proven method) where you fetch the pointer based on an ID right before using it -- as a side-note, in a multi-threaded application you can lock attempts to resize the underlying store whilst such a weak reference lives.



So your consumer will store an ID, and will fetch a pointer to the data from the "store" on demand. This also gives you control over all "fetches", so that you can track them, implement safety measure, etc.



void consumer::foo() {
thing *t = m_thing_store.get(m_thing_id);
if (t) {
// do something with t
}
}


Or more advanced alternative to help with synchronization in multi-threaded scenario:



void consumer::foo() {
reference<thing> t = m_thing_store.get(m_thing_id);
if (!t.empty()) {
// do something with t
}
}


Where reference would be some thread-safe RAII "weak pointer".



There are multiple ways of implementing this. You can either use an open-addressing hash table and use the ID as a key; this will give you roughly O(1) access time if you balance it properly.



Another alternative (best-case O(1), worst-case O(N)) is to use a "reference" structure, with a 32-bit ID and a 32-bit index (so same size as 64-bit pointer) -- the index serves as a sort-of cache. When you fetch, you first try the index, if the element in the index has the expected ID you are done. Otherwise, you get a "cache miss" and you do a linear scan of the store to find the element based on ID, and then you store the last-known index value in your reference.







share|improve this answer














share|improve this answer



share|improve this answer








edited May 27 at 11:07

























answered May 27 at 11:00









odyss-jiiodyss-jii

2,1278 silver badges18 bronze badges




2,1278 silver badges18 bronze badges
















  • Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

    – Igor G
    May 27 at 11:15











  • @IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

    – odyss-jii
    May 27 at 11:21











  • I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

    – j00hi
    May 27 at 16:53



















  • Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

    – Igor G
    May 27 at 11:15











  • @IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

    – odyss-jii
    May 27 at 11:21











  • I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

    – j00hi
    May 27 at 16:53

















Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

– Igor G
May 27 at 11:15





Accessing a 'thing' by ID brings some new problems: what if a given ID is reused by another thing (like in the ABA problem), what if a consumer needs RAII but the 'thing' isn't there come destruction time, is the performance of that fetch-by-id method important?

– Igor G
May 27 at 11:15













@IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

– odyss-jii
May 27 at 11:21





@IgorG true, but there are decent battle-proven defaults for these. For the ID, use ever-increasing sequence + interlocked increment (lock xadd) via std::atomic. As for the ownership of thing: with this solution the consumer may never own thing, the store owns it. So no consumer is allowed to assume that thing will exist at any time, it must always be checked. That is also what guarantees the memory-safety, but you must design around this principle. The performance of fetch-by-id will probably be important. If done correctly, ex. open-addressing hash table, it will be very fast.

– odyss-jii
May 27 at 11:21













I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

– j00hi
May 27 at 16:53





I like this answer (and do not understand why it has been downvoted) because it offers a different, yet viable approach to the problem. Isn't this approach exactly what APIs like OpenGL or Vulkan do when referring to resources? I mean, I don't know how they handle it internally, but I can imagine them to handle it like proposed in this answer since they always return consecutive numbers for handles which point to resources like textures or GPU-buffers. Those numbers are also referred to as "names" of a resource.

– j00hi
May 27 at 16:53











-1
















IMO best approach would be create new container which will behave is safe way.



Pros:




  • change will be done on separate level of abstraction

  • changes to old code will be minimal (just replace std::vector with new container).

  • it will be "clean code" way to do it


Cons:




  • it may look like there is a bit more work to do


Other answer proposes use of std::list which will do the job, but with larger number of allocation and slower random access. So IMO it is better to compose own container from couple of std::vectors.



So it may start look more or less like this (minimum example):



template<typename T>
class cluster_vector
{
public:
static const constexpr cluster_size = 16;

cluster_vector() {
clusters.reserve(1024);
add_cluster();
}

...

size_t size() const {
if (clusters.empty()) return 0;
return (clusters.size() - 1) * cluster_size + clusters.back().size();
}

T& operator(size_t index) {
thowIfIndexToBig(index);
return clusters[index / cluster_size][index % cluster_size];
}

void push_back(T&& x) {
if_last_is_full_add_cluster();
clusters.back().push_back(std::forward<T>(x));
}

private:
void thowIfIndexToBig(size_t index) const {
if (index >= size()) {
throw std::out_of_range("cluster_vector out of range");
}
}

void add_cluster() {
clusters.push_back({});
clusters.back().reserve(cluster_size);
}

void if_last_is_full_add_cluster() {
if (clusters.back().size() == cluster_size) {
add_cluster();
}
}

private:
std::vector<std::vector<T>> clusters;
}


This way you will provide container which will not reallocate items. It doesn't meter what T does.






share|improve this answer























  • 3





    Downvote: suggesting to "roll your own" (when standard solutions exist)

    – darune
    May 27 at 11:38













  • you mean std::list? It is not like std::list.

    – Marek R
    May 27 at 11:41
















-1
















IMO best approach would be create new container which will behave is safe way.



Pros:




  • change will be done on separate level of abstraction

  • changes to old code will be minimal (just replace std::vector with new container).

  • it will be "clean code" way to do it


Cons:




  • it may look like there is a bit more work to do


Other answer proposes use of std::list which will do the job, but with larger number of allocation and slower random access. So IMO it is better to compose own container from couple of std::vectors.



So it may start look more or less like this (minimum example):



template<typename T>
class cluster_vector
{
public:
static const constexpr cluster_size = 16;

cluster_vector() {
clusters.reserve(1024);
add_cluster();
}

...

size_t size() const {
if (clusters.empty()) return 0;
return (clusters.size() - 1) * cluster_size + clusters.back().size();
}

T& operator(size_t index) {
thowIfIndexToBig(index);
return clusters[index / cluster_size][index % cluster_size];
}

void push_back(T&& x) {
if_last_is_full_add_cluster();
clusters.back().push_back(std::forward<T>(x));
}

private:
void thowIfIndexToBig(size_t index) const {
if (index >= size()) {
throw std::out_of_range("cluster_vector out of range");
}
}

void add_cluster() {
clusters.push_back({});
clusters.back().reserve(cluster_size);
}

void if_last_is_full_add_cluster() {
if (clusters.back().size() == cluster_size) {
add_cluster();
}
}

private:
std::vector<std::vector<T>> clusters;
}


This way you will provide container which will not reallocate items. It doesn't meter what T does.






share|improve this answer























  • 3





    Downvote: suggesting to "roll your own" (when standard solutions exist)

    – darune
    May 27 at 11:38













  • you mean std::list? It is not like std::list.

    – Marek R
    May 27 at 11:41














-1














-1










-1









IMO best approach would be create new container which will behave is safe way.



Pros:




  • change will be done on separate level of abstraction

  • changes to old code will be minimal (just replace std::vector with new container).

  • it will be "clean code" way to do it


Cons:




  • it may look like there is a bit more work to do


Other answer proposes use of std::list which will do the job, but with larger number of allocation and slower random access. So IMO it is better to compose own container from couple of std::vectors.



So it may start look more or less like this (minimum example):



template<typename T>
class cluster_vector
{
public:
static const constexpr cluster_size = 16;

cluster_vector() {
clusters.reserve(1024);
add_cluster();
}

...

size_t size() const {
if (clusters.empty()) return 0;
return (clusters.size() - 1) * cluster_size + clusters.back().size();
}

T& operator(size_t index) {
thowIfIndexToBig(index);
return clusters[index / cluster_size][index % cluster_size];
}

void push_back(T&& x) {
if_last_is_full_add_cluster();
clusters.back().push_back(std::forward<T>(x));
}

private:
void thowIfIndexToBig(size_t index) const {
if (index >= size()) {
throw std::out_of_range("cluster_vector out of range");
}
}

void add_cluster() {
clusters.push_back({});
clusters.back().reserve(cluster_size);
}

void if_last_is_full_add_cluster() {
if (clusters.back().size() == cluster_size) {
add_cluster();
}
}

private:
std::vector<std::vector<T>> clusters;
}


This way you will provide container which will not reallocate items. It doesn't meter what T does.






share|improve this answer















IMO best approach would be create new container which will behave is safe way.



Pros:




  • change will be done on separate level of abstraction

  • changes to old code will be minimal (just replace std::vector with new container).

  • it will be "clean code" way to do it


Cons:




  • it may look like there is a bit more work to do


Other answer proposes use of std::list which will do the job, but with larger number of allocation and slower random access. So IMO it is better to compose own container from couple of std::vectors.



So it may start look more or less like this (minimum example):



template<typename T>
class cluster_vector
{
public:
static const constexpr cluster_size = 16;

cluster_vector() {
clusters.reserve(1024);
add_cluster();
}

...

size_t size() const {
if (clusters.empty()) return 0;
return (clusters.size() - 1) * cluster_size + clusters.back().size();
}

T& operator(size_t index) {
thowIfIndexToBig(index);
return clusters[index / cluster_size][index % cluster_size];
}

void push_back(T&& x) {
if_last_is_full_add_cluster();
clusters.back().push_back(std::forward<T>(x));
}

private:
void thowIfIndexToBig(size_t index) const {
if (index >= size()) {
throw std::out_of_range("cluster_vector out of range");
}
}

void add_cluster() {
clusters.push_back({});
clusters.back().reserve(cluster_size);
}

void if_last_is_full_add_cluster() {
if (clusters.back().size() == cluster_size) {
add_cluster();
}
}

private:
std::vector<std::vector<T>> clusters;
}


This way you will provide container which will not reallocate items. It doesn't meter what T does.







share|improve this answer














share|improve this answer



share|improve this answer








edited May 27 at 11:44

























answered May 27 at 11:33









Marek RMarek R

15.7k3 gold badges30 silver badges81 bronze badges




15.7k3 gold badges30 silver badges81 bronze badges











  • 3





    Downvote: suggesting to "roll your own" (when standard solutions exist)

    – darune
    May 27 at 11:38













  • you mean std::list? It is not like std::list.

    – Marek R
    May 27 at 11:41














  • 3





    Downvote: suggesting to "roll your own" (when standard solutions exist)

    – darune
    May 27 at 11:38













  • you mean std::list? It is not like std::list.

    – Marek R
    May 27 at 11:41








3




3





Downvote: suggesting to "roll your own" (when standard solutions exist)

– darune
May 27 at 11:38







Downvote: suggesting to "roll your own" (when standard solutions exist)

– darune
May 27 at 11:38















you mean std::list? It is not like std::list.

– Marek R
May 27 at 11:41





you mean std::list? It is not like std::list.

– Marek R
May 27 at 11:41











-2

















[A shared pointer] seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.




So what? Maybe the code is a little less self-documenting, but it will solve all your problems.
(And by the way you are muddling things by using the word "consumer", which in a traditional producer/consumer paradigm would take ownership.)



Also, returning a raw pointer in your current code is already entirely ambiguous as to ownership. In general, I'd say it's good practice to avoid raw pointers if you can (like you don't need to call delete.) I would return a reference if you go with unique_ptr



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing& get_thing_for_consumer() {
// some thing-selection logic
return *i_am_the_owner_of_things[5]; // 5 is just an example
}





share|improve this answer


























  • No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

    – j00hi
    May 28 at 13:01











  • "avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

    – formerlyknownas_463035818
    May 28 at 18:07
















-2

















[A shared pointer] seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.




So what? Maybe the code is a little less self-documenting, but it will solve all your problems.
(And by the way you are muddling things by using the word "consumer", which in a traditional producer/consumer paradigm would take ownership.)



Also, returning a raw pointer in your current code is already entirely ambiguous as to ownership. In general, I'd say it's good practice to avoid raw pointers if you can (like you don't need to call delete.) I would return a reference if you go with unique_ptr



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing& get_thing_for_consumer() {
// some thing-selection logic
return *i_am_the_owner_of_things[5]; // 5 is just an example
}





share|improve this answer


























  • No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

    – j00hi
    May 28 at 13:01











  • "avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

    – formerlyknownas_463035818
    May 28 at 18:07














-2














-2










-2










[A shared pointer] seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.




So what? Maybe the code is a little less self-documenting, but it will solve all your problems.
(And by the way you are muddling things by using the word "consumer", which in a traditional producer/consumer paradigm would take ownership.)



Also, returning a raw pointer in your current code is already entirely ambiguous as to ownership. In general, I'd say it's good practice to avoid raw pointers if you can (like you don't need to call delete.) I would return a reference if you go with unique_ptr



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing& get_thing_for_consumer() {
// some thing-selection logic
return *i_am_the_owner_of_things[5]; // 5 is just an example
}





share|improve this answer














[A shared pointer] seems like the worst approach to me, because a "thing" shall not own a "consumer" and with shared pointers I would create shared ownership.




So what? Maybe the code is a little less self-documenting, but it will solve all your problems.
(And by the way you are muddling things by using the word "consumer", which in a traditional producer/consumer paradigm would take ownership.)



Also, returning a raw pointer in your current code is already entirely ambiguous as to ownership. In general, I'd say it's good practice to avoid raw pointers if you can (like you don't need to call delete.) I would return a reference if you go with unique_ptr



std::vector<std::unique_ptr<thing>> i_am_the_owner_of_things;
thing& get_thing_for_consumer() {
// some thing-selection logic
return *i_am_the_owner_of_things[5]; // 5 is just an example
}






share|improve this answer












share|improve this answer



share|improve this answer










answered May 27 at 19:47









IceGlassesIceGlasses

673 bronze badges




673 bronze badges
















  • No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

    – j00hi
    May 28 at 13:01











  • "avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

    – formerlyknownas_463035818
    May 28 at 18:07



















  • No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

    – j00hi
    May 28 at 13:01











  • "avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

    – formerlyknownas_463035818
    May 28 at 18:07

















No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

– j00hi
May 28 at 13:01





No, shared pointers are there to express ownership. And the "consumer" in my example shall NOT get ownership of "thing" as I have clearly stated. Citing Herb Sutter in his great talk Back to the Basics! Essentials of Modern C++ Style: Non-owning raw pointers are still great.

– j00hi
May 28 at 13:01













"avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

– formerlyknownas_463035818
May 28 at 18:07





"avoid raw pointers" is a myth. It is raw owning pointers that should be avoided. Then there is also no ambiguity, raw pointers dont own stuff

– formerlyknownas_463035818
May 28 at 18:07



















draft saved

draft discarded



















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56324397%2fcustom-allocators-as-alternatives-to-vector-of-smart-pointers%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Færeyskur hestur Heimild | Tengill | Tilvísanir | LeiðsagnarvalRossið - síða um færeyska hrossið á færeyskuGott ár hjá færeyska hestinum

He _____ here since 1970 . Answer needed [closed]What does “since he was so high” mean?Meaning of “catch birds for”?How do I ensure “since” takes the meaning I want?“Who cares here” meaningWhat does “right round toward” mean?the time tense (had now been detected)What does the phrase “ring around the roses” mean here?Correct usage of “visited upon”Meaning of “foiled rail sabotage bid”It was the third time I had gone to Rome or It is the third time I had been to Rome

Slayer Innehåll Historia | Stil, komposition och lyrik | Bandets betydelse och framgångar | Sidoprojekt och samarbeten | Kontroverser | Medlemmar | Utmärkelser och nomineringar | Turnéer och festivaler | Diskografi | Referenser | Externa länkar | Navigeringsmenywww.slayer.net”Metal Massacre vol. 1””Metal Massacre vol. 3””Metal Massacre Volume III””Show No Mercy””Haunting the Chapel””Live Undead””Hell Awaits””Reign in Blood””Reign in Blood””Gold & Platinum – Reign in Blood””Golden Gods Awards Winners”originalet”Kerrang! Hall Of Fame””Slayer Looks Back On 37-Year Career In New Video Series: Part Two””South of Heaven””Gold & Platinum – South of Heaven””Seasons in the Abyss””Gold & Platinum - Seasons in the Abyss””Divine Intervention””Divine Intervention - Release group by Slayer””Gold & Platinum - Divine Intervention””Live Intrusion””Undisputed Attitude””Abolish Government/Superficial Love””Release “Slatanic Slaughter: A Tribute to Slayer” by Various Artists””Diabolus in Musica””Soundtrack to the Apocalypse””God Hates Us All””Systematic - Relationships””War at the Warfield””Gold & Platinum - War at the Warfield””Soundtrack to the Apocalypse””Gold & Platinum - Still Reigning””Metallica, Slayer, Iron Mauden Among Winners At Metal Hammer Awards””Eternal Pyre””Eternal Pyre - Slayer release group””Eternal Pyre””Metal Storm Awards 2006””Kerrang! Hall Of Fame””Slayer Wins 'Best Metal' Grammy Award””Slayer Guitarist Jeff Hanneman Dies””Bullet-For My Valentine booed at Metal Hammer Golden Gods Awards””Unholy Aliance””The End Of Slayer?””Slayer: We Could Thrash Out Two More Albums If We're Fast Enough...””'The Unholy Alliance: Chapter III' UK Dates Added”originalet”Megadeth And Slayer To Co-Headline 'Canadian Carnage' Trek”originalet”World Painted Blood””Release “World Painted Blood” by Slayer””Metallica Heading To Cinemas””Slayer, Megadeth To Join Forces For 'European Carnage' Tour - Dec. 18, 2010”originalet”Slayer's Hanneman Contracts Acute Infection; Band To Bring In Guest Guitarist””Cannibal Corpse's Pat O'Brien Will Step In As Slayer's Guest Guitarist”originalet”Slayer’s Jeff Hanneman Dead at 49””Dave Lombardo Says He Made Only $67,000 In 2011 While Touring With Slayer””Slayer: We Do Not Agree With Dave Lombardo's Substance Or Timeline Of Events””Slayer Welcomes Drummer Paul Bostaph Back To The Fold””Slayer Hope to Unveil Never-Before-Heard Jeff Hanneman Material on Next Album””Slayer Debut New Song 'Implode' During Surprise Golden Gods Appearance””Release group Repentless by Slayer””Repentless - Slayer - Credits””Slayer””Metal Storm Awards 2015””Slayer - to release comic book "Repentless #1"””Slayer To Release 'Repentless' 6.66" Vinyl Box Set””BREAKING NEWS: Slayer Announce Farewell Tour””Slayer Recruit Lamb of God, Anthrax, Behemoth + Testament for Final Tour””Slayer lägger ner efter 37 år””Slayer Announces Second North American Leg Of 'Final' Tour””Final World Tour””Slayer Announces Final European Tour With Lamb of God, Anthrax And Obituary””Slayer To Tour Europe With Lamb of God, Anthrax And Obituary””Slayer To Play 'Last French Show Ever' At Next Year's Hellfst””Slayer's Final World Tour Will Extend Into 2019””Death Angel's Rob Cavestany On Slayer's 'Farewell' Tour: 'Some Of Us Could See This Coming'””Testament Has No Plans To Retire Anytime Soon, Says Chuck Billy””Anthrax's Scott Ian On Slayer's 'Farewell' Tour Plans: 'I Was Surprised And I Wasn't Surprised'””Slayer””Slayer's Morbid Schlock””Review/Rock; For Slayer, the Mania Is the Message””Slayer - Biography””Slayer - Reign In Blood”originalet”Dave Lombardo””An exclusive oral history of Slayer”originalet”Exclusive! Interview With Slayer Guitarist Jeff Hanneman”originalet”Thinking Out Loud: Slayer's Kerry King on hair metal, Satan and being polite””Slayer Lyrics””Slayer - Biography””Most influential artists for extreme metal music””Slayer - Reign in Blood””Slayer guitarist Jeff Hanneman dies aged 49””Slatanic Slaughter: A Tribute to Slayer””Gateway to Hell: A Tribute to Slayer””Covered In Blood””Slayer: The Origins of Thrash in San Francisco, CA.””Why They Rule - #6 Slayer”originalet”Guitar World's 100 Greatest Heavy Metal Guitarists Of All Time”originalet”The fans have spoken: Slayer comes out on top in readers' polls”originalet”Tribute to Jeff Hanneman (1964-2013)””Lamb Of God Frontman: We Sound Like A Slayer Rip-Off””BEHEMOTH Frontman Pays Tribute To SLAYER's JEFF HANNEMAN””Slayer, Hatebreed Doing Double Duty On This Year's Ozzfest””System of a Down””Lacuna Coil’s Andrea Ferro Talks Influences, Skateboarding, Band Origins + More””Slayer - Reign in Blood””Into The Lungs of Hell””Slayer rules - en utställning om fans””Slayer and Their Fans Slashed Through a No-Holds-Barred Night at Gas Monkey””Home””Slayer””Gold & Platinum - The Big 4 Live from Sofia, Bulgaria””Exclusive! Interview With Slayer Guitarist Kerry King””2008-02-23: Wiltern, Los Angeles, CA, USA””Slayer's Kerry King To Perform With Megadeth Tonight! - Oct. 21, 2010”originalet”Dave Lombardo - Biography”Slayer Case DismissedArkiveradUltimate Classic Rock: Slayer guitarist Jeff Hanneman dead at 49.”Slayer: "We could never do any thing like Some Kind Of Monster..."””Cannibal Corpse'S Pat O'Brien Will Step In As Slayer'S Guest Guitarist | The Official Slayer Site”originalet”Slayer Wins 'Best Metal' Grammy Award””Slayer Guitarist Jeff Hanneman Dies””Kerrang! Awards 2006 Blog: Kerrang! Hall Of Fame””Kerrang! Awards 2013: Kerrang! Legend”originalet”Metallica, Slayer, Iron Maien Among Winners At Metal Hammer Awards””Metal Hammer Golden Gods Awards””Bullet For My Valentine Booed At Metal Hammer Golden Gods Awards””Metal Storm Awards 2006””Metal Storm Awards 2015””Slayer's Concert History””Slayer - Relationships””Slayer - Releases”Slayers officiella webbplatsSlayer på MusicBrainzOfficiell webbplatsSlayerSlayerr1373445760000 0001 1540 47353068615-5086262726cb13906545x(data)6033143kn20030215029