Is there a simple (generic) way, to make a container of std::unique_ptr's to appear as a container of raw pointers (especially, when used in range based for loops or algorithms)?
Background:
While reviewing some code I came across a few instances of the following pattern:
using namespace std; struct A { virtual void foo() const = 0; virtual ~A() {} }; struct B1 : A { void foo() const { cout << "Called B1::foo()" << endl; } }; struct B2 : A { void foo() const { cout << "Called B2::foo()" << endl; } }; class CustomList { vector<A*> list; public: void addB1() { list.push_back(new B1{}); } void addB2() { list.push_back(new B2{}); } const auto& getList() const { return list; } ~CustomList() { for (size_t i = 0; i < list.size(); ++i) { delete list[i]; } } }; void useA(const A* a) { a->foo(); } void useList() { CustomList cl; cl.addB1(); cl.addB2(); for (const auto& a : cl.getList()) { useA(a); } } For simplification I'd like to replace vector<A*> with vector<unique_ptr<A>>, but unfortunately, getList() leaks that implementation detail, so that useA(a) (and similar lines throughout the program) have to be replaced by useA(a.get()) which is pretty annoying and contradicts the simplification goal.
Now, I can basically think of two solutions:
- Add appropriate functions like
begin,endandoperator[]to theCustomListclasses (there are multiple different ones) making it unnecessary to expose the internal container class. - Write an proxy class that can be returned by
getList().
However, both cases would require some changes to the program and a transformation iterator and I'm starting to wonder whether the introduction of unique_ptr introduces more complexity than it removes.
So I wonder if there is a third, simpler solution to this.
NOTE:
- I'd prefer not to use boost::transform_iterator, as the project currently doesn't have a dependency on boost (the program is running on an embedded system).
- I'd also prefer not to create an actual container with pointer to the objects stored in the list as in this question, as this would introduce additional dynamic memory allocations and I often also only need to access a few items from the list.
- Finally, I'd also not want to change the interface of the functions represented by
useA. For one, there a quite a few of them and second, their use is not limited to this particular pattern, so an interface change would force changes in completely unrelated parts of the code.
For reference, here is my proxy version (any suggestions are welcome):
template<class BaseIterator> class TransformIterator: public BaseIterator { public: /* ## override basic type definitions ## */ using value_type = typename BaseIterator::value_type::pointer; using pointer = value_type*; using reference = value_type&; TransformIterator(const BaseIterator& other) : BaseIterator{ other } {} /* ## override/hide member functions, that dereferenciate the iterator ## */ value_type operator*() const { return this->BaseIterator::operator*().get(); } value_type operator[](size_t n) const { return this->BaseIterator::operator[](n).get(); } //overriding operator->() is not necessary, as value type (a pointer) isn't a struct or class }; template<class CONTAINER> class ContainerProxy { const CONTAINER& _list; public: ContainerProxy(const CONTAINER& list) : _list{ list } {} TransformIterator<vector<unique_ptr<A>>::const_iterator> begin() const { return _list.begin(); } TransformIterator<vector<unique_ptr<A>>::const_iterator> end() const { return _list.end(); } /* ... * other container functions like size, operator[] and conversion operator (operator const CONTAINER&()) */ }; class CustomList2 { vector<unique_ptr<A>> list; public: void addB1() { list.push_back(make_unique<B1>()); } void addB2() { list.push_back(make_unique<B2>()); } ContainerProxy<vector<unique_ptr<A>>> getList() const { return list; } };
useA()to accept astd::unique_ptr<A>&?begin(),end()etc.useA(which represents multiple different functions) due to a change in the implementation of an unrelated class. I agree however, that the exposing the internal representation is a significant problem on its own. The proxy-solution has the advantage that it requires less modifications in the code, but seems to be more like a dirty hack.useAshould probably be taking aconst A&anyway. Then you wouldn't have this problem..get()less as an annoyance or added complexity and more as explicit conversion from an owning pointer to a non-owning pointer. That conversion was happening before anyway it just wasn't clear.