vector of unique_ptr in C++11
Solution 1
I would suggest that instead of maintaining and returning an un-unique_ptr
ed vector, you provide functions to access the elements directly. This encapsulates the storage of your resources; clients don't know that they are stored as unique_ptr
s, nor that they are kept in a vector
.
One possibility for this is to use boost::indirect_iterator
to dereference your unique_ptr
automatically:
using ResourceIterator =
boost::indirect_iterator<std::vector<std::unique_ptr<HeavyResource>>::iterator,
const HeavyResource>;
ResourceIterator begin() { return std::begin(_vectorOfHeavyResources); }
ResourceIterator end() { return std::end(_vectorOfHeavyResources); }
Solution 2
If you encounter this often, it might make sense to write a class, that behaves like a unique_ptr, but passes the constness of the pointer to the object it points to. That way, you can just return a const reference to your vector.
I ended up writing this once and be done with it:
#include <iostream>
#include <vector>
#include <memory>
//unique,const-preserving pointer
template<class T>
class ucp_ptr {
std::unique_ptr<T> ptr;
public:
ucp_ptr() = default;
ucp_ptr(T* ptr) :ptr{ ptr }{};
ucp_ptr(std::unique_ptr<T>&& other) :ptr(std::move(other)){};
T& operator*() { return ptr.get(); }
T const & operator*()const { return ptr.get(); }
T* operator->() { return ptr.get(); }
T const * operator->()const { return ptr.get(); }
};
struct Foo {
int a = 0;
};
int main() {
std::vector<ucp_ptr<Foo>> v;
v.emplace_back(new Foo());
v.emplace_back(std::make_unique<Foo>());
v[0]->a = 1;
v[1]->a = 2;
const std::vector<ucp_ptr<Foo>>& cv = v;
std::cout << cv[0]->a << std::endl; //<-read access OK
//cv[1]->a = 10; //<-compiler error
}
Of course, you can extend it a bit, if you need custom deleters or want to add a specialization for managing arrays, but this is the base version. I also belive I've seen a more refined version of this somwhere here on SO, but I can't find it right now.
Here is an example, of how this can be used in a class:
class Bar {
std::vector<ucp_ptr<Foo>> v;
public:
void add(const Foo& foo){
v.push_back(std::make_unique<Foo>(foo));
}
//modifying elements
void doubleElements() {
for (auto& e : v){
e->a *= 2;
}
}
const std::vector<ucp_ptr<Foo>>& showElements() const{
return v;
}
};
EDIT
As far as your update is concerened, you have to live with the fact that vector<T>
is unrelated to vector<B>
even if it would be valid to cast T to B and vice versa.
You can write adaptors, that give you a different view to the elements (by casting each element when necessary) but - aside from creating a new vector of the proper type - there exists no general meachanism (that I am aware of) to do what you want.
Related videos on Youtube
Witek
Updated on September 16, 2022Comments
-
Witek over 1 year
I recently switched to C++11 and I'm trying to get used to good practices there. What I end up dealing with very often is something like:
class Owner { private: vector<unique_ptr<HeavyResource>> _vectorOfHeavyResources; public: virtual const vector<const HeavyResource*>* GetVectorOfResources() const; };
This requires me to do something like adding a _returnableVector and translating the source vectors to be able to return it later on:
_returnableVector = vector<HeavyResource*>; for (int i=0; i< _vectorOfHeavyResources.size(); i++) { _returnableVector.push_back(_vectorOfHeavyResources[i].get()); }
Has anyone noticed similar problem? What are your thoughts and solutions? Am I getting the whole ownership idea right here?
UPDATE: Heres another thing: What if one class returns a result of some processing as
vector<unique_ptr<HeavyResource>>
(it passes the ownership of the results to the caller), and it is supposed to be used for some subsequent processing:vector<unique_ptr<HeavyResource>> partialResult = _processor1.Process(); // translation auto result = _processor2.Process(translatedPartialResult); // the argument of process is vector<const HeavyResource*>
-
eerorika almost 9 yearsNot related to the constness problem (assuming that's important to your question), but if you want to share the pointers, then perhaps you don't actually want
unique_ptr
... -
Witek almost 9 yearsI dont want to share the OWNERSHIP, just allow to look into the resources.
-
-
Witek almost 9 yearsI would repate my comment from above just to encourage discussion about understanding of ownership: "I don't want to share the OWNERSHIP, just allow readonly access into the resources." In my understanding usage of shared_ptr as in makes the ownership distributed. IMO in proper architectures you can clearly point the ownership.
-
Galik almost 9 yearsI agree that
std::shared_ptr
should be avoided unless you can't control which object needs to delete the resources. -
Witek almost 9 yearsThis requires a class containing the GetVectorOfResources to be though of as a Collection. And it makes sense as I think about it. Heres another thing then: What if one class returns a result of some processing as
vector<unique_ptr<HeavyResource>>
(it passes the ownership of the results to the caller), and it is supposed to be used for some subsequent processing: vector<unique_ptr<HeavyResource>> result1 = _processor1.Process(); // translation auto result2 = _processor2.Process(translatedResult); // the argument of process is vector<const HeavyResource*>` -
TartanLlama almost 9 yearsIn that case, you could make
decltype(_processor2)::Process
take a range instead and pass indirect iterators to it. -
Witek almost 9 yearsI guess it will fail if you try to copy the returned vector.
-
Witek almost 9 yearsIt didn't... so that's also something to look into (time to understand reinterpret_cast ;) )
-
TartanLlama almost 9 yearsThis is very dangerous, as it assumes that
std::unique_ptr
just holds a single pointer, which is not necessarily the case, especially if you have a stateful custom deleter. -
Tas almost 9 yearsIt's entirely up to you; however, you were trying to use
unique_ptr
and noted that you were having difficulty, whereasshared_ptr
does what you need it to do. I would definitely useshared_ptr
if I had someResourceManager
and needed to temporarily lend out resources. As suggested, you could create helper access functions but that's tedious if callers are allowed to do multiple things with the object.shared_ptr
does what you need: it shares access. -
Arne Vogel almost 9 yearsType punning between unrelated non-POD types is not allowed by the standard, so this program is ill-formed. And the only efficiency gain is probably saving some typing.
-
Witek almost 9 yearsThe problem with this one is that once emplaced inside the Owner, even he's not allowed to modify his resources. But it might be useful.
-
MikeMB almost 9 years@Witek: Maybe I'm missing your point, but why shouldn't he? Can you give an example of what is not possible with this scheme?
-
MikeMB almost 9 yearsThis doesn't actually address the question. The OP wants to return a collection of pointers to
const
. Wether they are raw, unique or shared actually addresses a different issue and are only relevant for how exactly the methods have to be written. -
MikeMB almost 9 years@Witek: Sorry, there was a typo in my solution: the
operator*
of course returns aT&
not aT*
-
MikeMB almost 9 years@Arne Vogel: While I don't want to encourage any form of UB, it has a significant efficiency gain compared to the OPs solution, as one can now return a reference or pointer to the internal vector instead of creating new vector. But again, any reliance on UB is a catastrophy waiting to happen.
-
Witek almost 9 yearsYou are right - I misunderstood your code at first. I'm also quite surprised that const vector allows access to const elements!
-
Witek almost 9 yearsNow I can't understand why doesn't
unique_ptrs
work like yourucp_ptrs
?! Ifvector
maps its constness to its elements, why doesn'tunique_ptr
map its constness to its value? -
MikeMB almost 9 years@Witek: Essentially, because smart pointers are supposed to work like real pointers and there are a lot of instances, where you want a
T const*
orT* const
, but not aT const* const
.