I have this obsession with making things const that really should be const, even when it seems a bit painful to do so.
In this case, I want a vector<int> into which I can add elements, but whose existing elements I cannot change. The problem is that vector<const int> is not allowed.
The purpose is not only to prevent the users of my classes to modify things they shouldn't modify (this can be easily done by const iterators in member functions). It is also, just as importantly, so I myself don't later do things in my own code that I didn't intend on doing.
I have found two alternatives:
Use deque<const int> instead.
Create my own vector class that wraps the actual vector, like this (I do this for int here, but in reality I would use templates for generality)
class my_vector {
private:
std::vector<int> vec;
public:
std::vector<int>::const_iterator cbegin() { return vec.cbegin(); }
std::vector<int>::const_iterator cend() { return vec.cend(); }
void push_back(int i) { vec.push_back(i); }
};
Which of these ideas (or others) would you recommend? Will the compiler turn the second alternative into code that is just as fast as using the vector directly?
Your obsession is understandable and I do not disagree, but unless you have a really pressing need for it, the effort required might not be worth the effort.
Assuming you chose vector as your container based on your design requirement, I would not recommend changing it to deque, or any other type. It would only serve to confuse a future reader (which would likely be yourself), and is also a bad practice otherwise.
If we go with your wrapper solution, you would have to hide override all members which could modify the vector, which is a problem since there are a number of methods used to both access and modify, such as operator[], at(), front(), back(). This could confuse a future reader, since they are used to the "standard" vector behavior.
By the way, are you sure deque<const int> foo; works? I get the following error:
error: invalid conversion from 'const void*' to 'void*'
Edit It seems I didn't quite answer your question. If you have to pick one, I would choose the wrapper approach.
I prefer private inheritance for these sort of standard-container-almost-exactly-fits-my-needs cases. Public inheritance leads to slicing or non-virtual destruction through a pointer to a base class. Composition requires too much extra typing.
Private inheritance and the use of using expresses your intent well.
Here is an exmaple:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
class my_vector : private std::vector<int> {
// Utility type
typedef std::vector<int> base;
public:
// Types I don't need to tweak:
using base::value_type;
using base::allocator_type;
using base::size_type;
using base::difference_type;
using base::const_reference;
using base::const_pointer;
using base::const_iterator;
using base::const_reverse_iterator;
// Types I do need to tweak:
typedef const_reference reference;
typedef const_pointer pointer;
typedef const_iterator iterator;
typedef const_reverse_iterator reverse_iterator;
// Constructors
// Implicit constructors OK
// Destructors
// Implicit destructors OK
// Assignment
// Implicit assignment OK
// Methods that I don't need to tweak:
using base::assign;
using base::get_allocator;
using base::empty;
using base::size;
using base::max_size;
using base::reserve;
using base::capacity;
using base::clear;
using base::push_back;
using base::pop_back;
using base::swap;
// Methods I need to tweak
const_reference at( size_type pos ) const { return base::at(pos); }
const_reference operator[](size_type pos) const { return base::operator[](pos); }
const_reference front() const { return base::front(); }
const_reference back() const { return base::back(); }
const_iterator begin() const { return base::begin(); }
const_iterator end() const { return base::end(); }
const_reverse_iterator rbegin() const { return base::rbegin(); }
const_reverse_iterator rend() const { return base::rend(); }
// Methods I need to delete:
// base::insert;
// base::erase;
// base::resize;
};
int main () {
my_vector m;
m.push_back(1);
m.push_back(2);
m.push_back(3);
my_vector m2;
m2 = m;
std::copy(m2.begin(), m2.end(), std::ostream_iterator<int>(std::cout, "\n"));
}
There's nothing magic about the standard containers. If you need a container with characteristics that are different from those of the standard containers, write your own. If one of the standard containers is close to what you need, use that as the internal implementation. In short, my_vector is the way to go.
Related
Lets show it in an example where we have a Data class with primary data, some kind of index that points to the primary data, and we also need to expose a const version the index.
class Data
{
public:
const std::vector<int>& getPrimaryData() const { return this->primaryData; }
const std::vector<int*>& getIndex() const { return this->index; }
private:
std::vector<int> primaryData;
std::vector<int*> index;
};
This is wrong, as the user can easily modify the data:
const Data& data = something.getData();
const std::vector<int*>& index = data.getIndex();
*index[0] = 5; // oups we are modifying data of const object, this is wrong
The reason of this is, that the proper type the Data::getIndex should be returning is:
const std::vector<const int*>&
But you can guess what happens when you try to write the method that way to "just convert the non-const variant to const variant":
// compiler error, can't convert std::vector<int*> to std::vector<const int*> these are unrelated types.
const std::vector<const int*>& getIndex() const { return this->index; }
As far as I know, C++ doesn't have any good solution to this problem. Obviously, I could just create new vector, copy the values from the index and return it, but that doesn't make any sense from the performance perspective.
Please, note that this is just simplified example of real problems in bigger programs. int could be a bigger object (Book lets say), and index could be index of books of some sort. And the Data might need to use the index to modify the book, but at the same time, provide the index to read the books in a const way.
In C++20, you can just return a std::span with elements of type const int*
#include <vector>
#include <span>
class Data
{
public:
std::span<const int* const> getIndex() const { return this->index; }
private:
std::vector<int*> index;
};
int main() {
const Data data;
const auto index = data.getIndex();
*index[0] = 5; // error: assignment of read-only location
}
Demo
Each language has its rules and usages... std::vector<T> and std::vector<const T> are different types in C++, with no possibility to const_cast one into the other, full stop. That does not mean that constness is broken, it just means it is not the way it works.
For the usage part, returning a full container is generally seen as a poor encapsulation practice, because it makes the implementation visible and ties it to the interface. It would be better to have a method taking an index and returning a pointer to const (or a reference to a pointer to const if you need it):
const int* getIndex(int i) const { return this->index[i]; }
This works, because a T* can be const_casted to a const T *.
The top answer, using ranges or spans, is a great solution, if you can use C++20 or later (or a library such as the GSL). If not, here are some other approaches.
Unsafe Cast
#include <vector>
class Data
{
public:
const std::vector<const int>& getPrimaryData() const
{
return *reinterpret_cast<const std::vector<const int>*>(&primaryData);
}
const std::vector<const int* const>& getIndex()
{
return *reinterpret_cast<const std::vector<const int* const>*>(&index);
}
private:
std::vector<int> primaryData;
std::vector<int*> index;
};
This is living dangerously. It is undefined behavior. At minimum, you cannot count on it being portable. Nothing prevents an implementation from creating different template overloads for const std::vector<int> and const std::vector<const int> that would break your program. For example, a library might add some extra private data member to a vector of non-const elements that it doesn’t for a vector of const elements (which are discouraged anyway).
While I haven’t tested this extensively, it appears to work in GCC, Clang, ICX, ICC and MSVC.
Smart Array Pointers
The array specialization of the smart pointers allows casting from std::shared_ptr<T[]> to std::shared_ptr<const T[]> or std::weak_ptr<const T[]>. You might be able to use std::shared_ptr as an alternative to std::vector and std::weak_ptr as an alternative to a view of the vector.
#include <memory>
class Data
{
public:
std::weak_ptr<const int[]> getPrimaryData() const
{
return primaryData;
}
std::weak_ptr<const int* const[]> getIndex()
{
return index;
}
private:
std::shared_ptr<int[]> primaryData;
std::shared_ptr<int*[]> index;
};
Unlike the first approach, this is type-safe. Unlike a range or span, this has been available since C++11. Note that you would not actually want to return an incomplete type with no array bound—that’s just begging for a buffer overflow vulnerability—unless your client knew the size of the array by some other means. It would primarily be useful for fixed-size arrays.
Subranges
A good alternative to std::span is a std::ranges::subrange, which you can specialize on the const_iterator member type of your data. This is defined in terms of a begin and end iterator, rather than an iterator and size, and could even be used (with modification) for a container with non-contiguous storage.
This works in GCC 11, and with clang 14 with -std=c++20 -stdlib=libc++, but not all other compilers (as of 2022):
#include <ranges>
#include <vector>
class Data
{
private:
using DataType = std::vector<int>;
DataType primaryData;
using IndexType = std::vector<DataType::pointer>;
IndexType index;
public:
/* The types of views of primaryData and index, which cannot modify their contents.
* This is a borrowed range. It MUST NOT OUTLIVE the Data, or it will become a dangling reference.
*/
using DataView = std::ranges::subrange<DataType::const_iterator>;
// This disallows modifying either the pointers in the index or the data they reference.
using IndexView = std::ranges::subrange<const int* const *>;
/* According to the C++20 standard, this is legal. However, not all
* implementations of the STL that I tested conform to the requirement that
* std::vector::cbegin is contstexpr.
*/
constexpr DataView getPrimaryData() const noexcept
{
return DataView( primaryData.cbegin(), primaryData.cend() );
}
constexpr IndexView getIndex() const noexcept
{
return IndexView( index.data(), index.data() + index.size() );
}
};
You could define DataView as any type implementing the range interface, such as a std::span or std::string_view, and client code should still work.
You could return a transforming view to the vector. Example:
auto getIndex() const {
auto to_const = [](int* ptr) -> const int* {
return ptr;
};
return this->index | std::views::transform(to_const);
}
Edit: std::span is simpler option.
If index contains pointers to elements of primaryData, then you could solve the problem by instead storing integers representing the indices of the currently pointed objects. Anyone with access to non-const primaryData can easily turn those indices to pointers to non-const, others cannot.
primaryData isn't stable,
If primaryData isn't stable, and index contains pointers to primaryData, then the current design is broken because those pointers would be invalidated. The integer index alternative fixes this as long as the indices remain stable (i.e. you only insert to back). If even the indices aren't stable, then you are using a wrong data structure. Linked list and a vector of iterators to the linked list could work.
You're asking for std::experimental::propagate_const. But since it is an experimental feature, there is no guarantee that any specific toolchain is shipped with an implementation. You may consider implementing your own. There is an MIT licensed implementation, however. After including the header:
using namespace xpr=std::experimental;
///...
std::vector<xpr::propagate_const<int*>> my_ptr_vec;
Note however that raw pointer is considered evil so you may need to use std::unique_ptr or std::shared_ptr. propagate_const is supposed to accept smart pointers as well as raw pointer types.
As mentioned in a comment, you can do this:
class Data
{
public:
const std::vector<int>& getPrimaryData() const { return this->primaryData; }
const std::vector<const int*>& getIndex() const { return this->index; }
private:
std::vector<int> primaryData;
std::vector<const int*> index;
int* read_index_for_writing(std::size_t i) { return const_cast<int*>(index[i]); }
};
Good things about this solution: it works, and is safe, in every version of the standard and every compliant implementation. And it returns a vector reference with no funny wrapper classes – which probably doesn't matter to the caller, but it might.
Bad: you have to use the helper method internally, though only when reading the index for the purpose of writing the data. And the commenter described it as "dirty", but it seems clean enough to me.
Prepare the type like following, and use as return type of Data::getIndex().
class ConstIndex
{
private:
const std::vector<int*> &index;
public:
ConstIndex( const std::vector<int*> &index ) : index(index) {}
public:
//Implement methods/types needed to emulate "const std::vector<const int*>"
const int *operator[]( size_t i ) const { return index[i]; }
const int *at( size_t i ) const { return index.at(i); }
...
};
Here is an ugly solution that works with versions before C++20 using reinterpret_cast:
const std::vector<const int*>& getIndex() const{
return reinterpret_cast<const std::vector<const int*>&>(data);
}
Note this does actually return a reference bound to an lvalue, not a const& bound to an rvalue:
std::vector<const int*>& getIndex() const{
return reinterpret_cast<std::vector<const int*>&>(data);
}
I have a pointer to a list of pointers, as a private variable. I also have a getter that returns the pointer to the list. I need to protect it from changes.
I couldn't find how to use reinterpret_cast or const_cast on this.
class typeA{
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};
The compiler returns:
error: could not convert ‘((typeA*)this)->typeA::x’ from ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<typeB> > >’ to ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<const typeB> > >’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
It seems as const shared_ptr<list<shared_ptr<typeB>>> and shared_ptr<const list<shared_ptr<typeB>>> work fine.
Is it possible to do return l as a complete const, like:
const shared_ptr<const list<shared_ptr<const typeB>>>
or at least like:
shared_ptr<list<shared_ptr<const typeB>>>
?
References instead of pointers is not an option. To declare l as shared_ptr<list<shared_ptr<const typeB>>> also is not a wanted solution.
EDIT: no 'int' anymore.
It seems as it is not possible exactly what I wanted, but the suggested solutions are good. Yes, copying pointers is acceptable.
My bad i didn't put typeB immediately. I am aware of some advantages of references over pointers, but I hoped there is some similar solution.
You can create a new list of const int's from your original list and return that:
std::shared_ptr<std::list<std::shared_ptr<const int>>> getList(){
return std::make_shared<std::list<std::shared_ptr<const int>>>(l->begin(), l->end());
}
If you want to prevent people from making changes to the returned list, make it const too:
std::shared_ptr<const std::list<std::shared_ptr<const T>>> getList(){
return std::make_shared<const std::list<std::shared_ptr<const T>>>(l->cbegin(), l->cend());
}
The shared pointer returned by this function does not point to the original list but to the newly created list.
An alternative may be to provide iterators that, when dereferenced, returns const T& (where T is the type you actually store). That way there will be no need to copy the whole list every time you want to go though it. Example:
#include <iostream>
#include <list>
#include <memory>
struct example {
int data;
example(int x) : data(x) {}
};
template <class T>
class typeA {
std::shared_ptr<std::list<std::shared_ptr<T>>> l = std::make_shared<std::list<std::shared_ptr<T>>>();
public:
template< class... Args >
void add( Args&&... args ) {
l->emplace_back(std::make_shared<T>(std::forward<Args>(args)...));
}
// a very basic iterator that can be extended as needed
struct const_iterator {
using uiterator = typename std::list<std::shared_ptr<T>>::const_iterator;
uiterator lit;
const_iterator(uiterator init) : lit(init) {}
const_iterator& operator++() { ++lit; return *this; }
const T& operator*() const { return *(*lit).get(); }
bool operator!=(const const_iterator& rhs) const { return lit != rhs.lit; }
};
const_iterator cbegin() const noexcept { return const_iterator(l->cbegin()); }
const_iterator cend() const noexcept { return const_iterator(l->cend()); }
auto begin() const noexcept { return cbegin(); }
auto end() const noexcept { return cend(); }
};
int main() {
typeA<example> apa;
apa.add(10);
apa.add(20);
apa.add(30);
for(auto& a : apa) {
// a.data = 5; // error: assignment of member ‘example::data’ in read-only object
std::cout << a.data << "\n";
}
}
When you convert a pointer-to-nonconst to a pointer-to-const, you have two pointers. Furthermore, a list of pointers-to-nonconst is a completely different type from a list of pointers-to-const.
Thus, if you want to return a pointer to a list of pointers-to-const, what you must have is a list of pointers-to-const. But you don't have such list. You have a list of pointers-to-nonconst and those list types are not interconvertible.
Of course, you could transform your pointers-to-nonconst into a list of pointers-to-const, but you must understand that it is a separate list. A pointer to former type cannot point to the latter.
So, here is an example to transform the list (I didn't test, may contain typos or mistakes):
list<shared_ptr<const int>> const_copy_of_list;
std::transform(l->begin(), l->end(), std::back_inserter(const_copy_of_list),
[](auto& ptr) {
return static_pointer_cast<const int>(ptr);
});
// or more simply as shown by Ted:
list<shared_ptr<const int>> const_copy_of_list(l->begin(), l->end());
Since we have created a completely new list, which cannot be pointed by l, it makes little sense to return a pointer. Let us return the list itself. The caller can wrap the list in shared ownership if the need it, but don't have to when it is against their needs:
list<shared_ptr<const int>> getConstCopyList() {
// ... the transorm above
return const_copy_of_list;
}
Note that while the list is separate, the pointers inside still point to the same integers.
As a side note, please consider whether shared ownership of an int object makes sense for your program - I'm assuming it is a simplification for the example.
Also reconsider whether "References instead of pointers is not an option" is a sensible requirement.
You problem squarely lies at
but I do not want to mix references and pointers. It is easier and cleaner to have just pointers.
What you are finding here is that statement is wrong. A list<TypeB> can bind a const list<TypeB> & reference, and none of the list's members will allow any modification of the TypeB objects.
class typeA {
std::vector<typeB> l;
public:
const std::vector<typeB> & getList() const { return l; };
};
If you really really must have const typeB, you could instead return a projection of l that has added const, but that wouldn't be a Container, but instead a Range (using the ranges library voted into C++20, see also its standalone implementation)
std::shared_ptr<const typeB> add_const(std::shared_ptr<typeB> ptr)
{
return { ptr, ptr.get() };
}
class typeA {
std::vector<std::shared_ptr<typeB>> l;
public:
auto getList() const { return l | std::ranges::transform(add_const); };
};
Another alternative is that you can wrap your std::shared_ptrs in something like std::experimental::propagate_const, and just directly return them.
What you have here is a VERY complex construct:
shared_ptr<list<shared_ptr<typeB>>> l;
This is three levels of indirection, of which two have reference counting lifetime management, and the third is a container (and not memory-contiguous at that).
Naturally, given this complex structure, it is not going to be easy to convert it to another type:
shared_ptr<list<shared_ptr<const typeB>>>
Notice that std::list<A> and std::list<const A> are two distinct types by design of standard library. When you want to pass around non-modifying handles to your containers, you are usually supposed to use const_iterators.
In your case there is a shared_ptr on top of the list, so you can't use iterators if you want that reference counting behavior.
At this point comes the question: do you REALLY want that behavior?
Are you expecting a situation where your typeA instance is destroyed, but you still have some other typeA instances with the same container?
Are you expecting a situation where all your typeA instances sharing the container are destroyed, but you still have some references to that container in other places of your runtime?
Are you expecting a situation where the container itself is destroyed, but you still have some references to some of the elements?
Do you have any reason at all to use std::list instead of more conventional containers to store shared pointers?
If you answer YES to all the bullet points, then to achieve your goal you'll probably have to design a new class that would behave as a holder for your shared_ptr<list<shared_ptr<typeB>>>, while only providing const access to the elements.
If, however, on one of the bullet points your answer is NO, consider redesigning the l type. I suggest starting with std::vector<typeB> and then only adding necessary modifications one by one.
The problem with templates is that for any
template <typename T>
class C { };
any two pairs C<TypeA> and C<TypeB> are totally unrelated classes – this is even the case if TypeA and TypeB only differ in const-ness.
So what you actually want to have is technically not possible. I won't present a new workaround for now, as there are already, but try to look a bit further: As denoted in comments already, you might be facing a XY problem.
Question is: What would a user do with such a list? She/he might be iterating over it – or access single elements. Then why not make your entire class look/behave like a list?
class typeA
{
// wondering pretty much why you need a shared pointer here at all!
// (instead of directly aggregating the list)
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<typeB>>>::const_iterator begin() { return l->begin(); }
shared_ptr<list<shared_ptr<typeB>>>::const_iterator end() { return l->end(); }
};
If you used a vector instead of a list, I'd yet provide an index operator:
shared_ptr<typeB /* const or not? */> operator[](size_t index);
Now one problem yet remains unsolved so far: The two const_iterators returned have an immutable shared pointer, but the pointee is still mutable!
This is a bit of trouble - you'll need to implement your own iterator class now:
class TypeA
{
public:
class iterator
{
std::list<std::shared_ptr<int>>::iterator i;
public:
// implementation as needed: operators, type traits, etc.
};
};
Have a look at std::iterator for a full example – be aware, though, that std::iterator is deprecated, so you'll need to implement the type-traits yourself.
The iterator tag to be used would be std::bidirectional_iterator_tag or random_access_iterator_tag (contiguous_iterator_tag with C++20), if you use a std::vector inside.
Now important is how you implement two of the needed operators:
std::shared_ptr<int const> TypeA::iterator::operator*()
{
return std::shared_ptr<int const>(*i);
}
std::shared_ptr<int const> TypeA::iterator::operator->()
{
return *this;
}
The other operators would just forward the operation to the internal iterators (increment, decrement if available, comparison, etc).
I do not claim this is the Holy Grail, the path you need to follow under all circumstances. But it is a valuable alternative worth to at least consider...
I need to implement iterators, and i don't have time to make nice iterator classes, so i have decided to just return pointers. It is something like this
int* begin()
{
return p;
}
But i want them to behave as usual stl iterators
*++begin(); // doesn't work because returned pointer isn't l-value
std::vector<int> vi{ 0, 1 };
*++vi.begin(); // works fine
int* p = begin();
*++p; // works fine as well
How can i do this?
Pointers do meet the iterator requirements prefectly (a pointer meets even the most-specialised Random access iterator requirements). Your problem comes from the fact that in the implementation of the standard library which you're using, the iterators provided by e.g. std::vector support more operations than the iterator requirements require.
In other words, the standard does not guarantee that ++vi.begin() will work for a std::vector iterator vi. It happens to work on your implementation of the standard library, but it's an implementation detail. An iterator which would not support that is still a perfectly valid iterator.
So, to answer your question: if you want a quick stand-in for an iterator which will support all iterator operations, you can certainly use a pointer. If you want a quick stand-in for an iterator which will additionally support all the operations your standard library implementation supports in addition to iterator requirements, you might have to roll out your own class.
A minimal iterator is quite easy to whip up using boost:
#include <boost/iterator/iterator_facade.hpp>
using namespace boost;
struct Int100
{
int arr[100];
struct iterator : iterator_facade<iterator,int,forward_traversal_tag>
{
iterator( int* p = nullptr ) : p(p) {}
void increment() { ++p; }
bool equal(const iterator& other) const { return p == other.p; }
int& dereference() const { return *p; }
int* p;
};
iterator begin() { return {arr}; }
iterator end() { return {arr+100}; }
};
This supports the *++begin() syntax you where looking for.
I often find myself wanting to write code like this:
class MyClass
{
public:
void addObject(std::unique_ptr<Object>&& newObject);
void removeObject(const Object* target);
private:
std::set<std::unique_ptr<Object>> objects;
};
However, much of the std::set interface is kind of useless with std::unique_ptrs since the lookup functions require std::unique_ptr parameters (which I obviously don't have because they're owned by the set itself).
I can think of two main solutions to this.
Create a temporary unique_ptr for lookup. For example, the above removeObject() could be implemented like:
void MyClass::removeObject(const Object* target)
{
std::unique_ptr<Object> targetSmartPtr(target);
objects.erase(targetSmartPtr);
targetSmartPtr.release();
}
Replace the set with a map of raw pointers to unique_ptrs.
// ...
std::map<const Object*, std::unique_ptr<Object>> objects;
};
However, both seem slightly stupid to me. In solution 1, erase() isn't noexcept, so the temporary unique_ptr might delete the object it doesn't really own, and 2 requires double the storage for the container unnecessarily.
I know about Boost's pointer containers, but their current features are limited compared to modern C++11 standard library containers.
I was recently reading about C++14 and came across "Adding heterogeneous comparison lookup to associative containers". But form my understanding of it, the lookup types must be comparable to the key types, but raw pointers aren't comparable to unique_ptrs.
Anyone know of a more elegant solution or an upcoming addition to C++ that solves this problem?
In C++14, std::set<Key>::find is a template function if Compare::is_transparent exists. The type you pass in does not need to be Key, just equivalent under your comparator.
So write a comparator:
template<class T>
struct pointer_comp {
typedef std::true_type is_transparent;
// helper does some magic in order to reduce the number of
// pairs of types we need to know how to compare: it turns
// everything into a pointer, and then uses `std::less<T*>`
// to do the comparison:
struct helper {
T* ptr;
helper():ptr(nullptr) {}
helper(helper const&) = default;
helper(T* p):ptr(p) {}
template<class U, class...Ts>
helper( std::shared_ptr<U,Ts...> const& sp ):ptr(sp.get()) {}
template<class U, class...Ts>
helper( std::unique_ptr<U, Ts...> const& up ):ptr(up.get()) {}
// && optional: enforces rvalue use only
bool operator<( helper o ) const {
return std::less<T*>()( ptr, o.ptr );
}
};
// without helper, we would need 2^n different overloads, where
// n is the number of types we want to support (so, 8 with
// raw pointers, unique pointers, and shared pointers). That
// seems silly:
// && helps enforce rvalue use only
bool operator()( helper const&& lhs, helper const&& rhs ) const {
return lhs < rhs;
}
};
then use it:
typedef std::set< std::unique_ptr<Foo>, pointer_comp<Foo> > owning_foo_set;
now, owning_foo_set::find will accept unique_ptr<Foo> or Foo* or shared_ptr<Foo> (or any derived class of Foo) and find the correct element.
Outside of C++14, you are forced to use the map to unique_ptr approach, or something equivalent, as the signature of find is overly restrictive. Or write your own set equivalent.
Another possibility, close to the accepted answer, but a little different and simplified.
We can exploit the fact that standard comparator std::less<> (with no template arguments) is transparent. Then, we can supply our own comparison functions in the global namespace:
// These two are enough to be able to call objects.find(raw_ptr)
bool operator<(const unique_ptr<Object>& lhs, const Object* rhs) {
return std::less<const Object*>()(lhs.get(), rhs);
}
bool operator<(const Object* lhs, const unique_ptr<Object>& rhs) {
return std::less<const Object*>()(lhs, rhs.get());
}
class MyClass
{
// ...
private:
std::set<std::unique_ptr<Object>, std::less<>> objects; // Note std::less<> here
};
You can try to use boost::multi_index_container with additional indexing by Object*.
Something like this:
typedef std::unique_ptr<Object> Ptr;
typedef multi_index_container<
Ptr,
indexed_by<
hashed_unique<Ptr>,
ordered_unique<const_mem_fun<Ptr,Object*,&Ptr::get> >
>
> Objects;
Fore more information see Boost Multi-index Containers documentation
Or may be you can use std::shared_ptr everywhere, or use raw pointers in set instead?
Why you need to lookup by raw pinter? If you store it anywhere and check that object with this pointer is valid then better to use std::shared_ptr for storing in container and std::weak_ptr for other objects. In this case before usage you don't need lookup by raw pointer at all.
While definitely a hack, I just realized it's possible to construct a temporary "dumb" unique_ptr with placement new and not risk de-allocation. removeObject() could be written something like this:
void MyClass::removeObject(const Object* target)
{
alignas(std::unique_ptr<Object>)
char dumbPtrData[sizeof(std::unique_ptr<Object>)];
objects.erase(
*::new (dumbPtrData) std::unique_ptr<Object>(const_cast<Object *>(target)));
}
This solution would work for std::unordered_set, std::map keys, and std::unordered_map keys as well, all using standard C++11 only, with practically zero unnecessary overhead.
UPDATE 2: Yakk is correct, there is no way to do this with standard C++11 containers without significant compromises. Either something will run in linear time in the worst case or there are those workarounds that you write in your question.
There are two workarounds that I would consider.
I would try a sorted std::vector, similarly to boost::container::flat_set. Yes, the inserts / erases will be linear time in the worst case. Still, it might be much faster than you probably think: Contiguous containers are very cache friendly compared to node based containers, such as std::set. Please read what they write at boost::container::flat_set. Whether this compromise is acceptable for you, I cannot tell / measure.
Others also mentioned std::share_ptr. I personally try to avoid them, mainly because "a shared pointer is as good as a global variable" (Sean Parent). Another reason why I don't use them is because they are heavy weight, partly because of all the multi-threading stuff that I usually don't need. However, boost::shared_ptr, when BOOST_SP_DISABLE_THREADS is defined, removes all that overhead associated with multi-threading. I believe using boost::shared_ptr would be the easiest solution in your case.
UPDATE: As Yakk kindly pointed out, my approach has linear time complexity... :(
(The first version.)
You can do it by passing a custom comparator to std::lower_bound(). Here is a rudimentary implementation how:
#include <algorithm>
#include <cassert>
#include <iostream>
#include <memory>
#include <set>
#include <string>
using namespace std;
template <typename T>
class Set {
private:
struct custom_comparator {
bool operator()(const unique_ptr<T>& a, const T* const & b){
return a.get() < b;
}
} cmp;
set<unique_ptr<T>> objects; // decltype at begin() and end()
// needs objects to be declared here
public:
auto begin() const -> decltype(objects.begin()) { return objects.begin(); }
auto end() const -> decltype(objects.end() ) { return objects.end(); }
void addObject(unique_ptr<T>&& newObject) {
objects.insert(move(newObject));
}
void removeObject(const T* target) {
auto pos = lower_bound(objects.begin(), objects.end(), target, cmp);
assert (pos!=objects.end()); // What to do if not found?
objects.erase(pos);
}
};
void test() {
typedef string T;
Set<T> mySet;
unique_ptr<T> a{new T("a")};
unique_ptr<T> b{new T("b")};
unique_ptr<T> c{new T("c")};
T* b_ptr = b.get();
mySet.addObject(move(a));
mySet.addObject(move(b));
mySet.addObject(move(c));
cout << "The set now contains: " << endl;
for (const auto& s_ptr : mySet) {
cout << *s_ptr << endl;
}
mySet.removeObject(b_ptr);
cout << "After erasing b by the pointer to it:" << endl;
for (const auto& s_ptr : mySet) {
cout << *s_ptr << endl;
}
}
int main() {
test();
}
You're using unique pinters here. This means, your set has unique ownership of objects. Now, this should mean that if object does exist, it's either in the set or you have unique pointer with it. You don't even need to look up the set in this case.
But to me it looks like it's not hte case. I suppose you're better off with shared pointer in this case. Just store shared pointers and pass them around since someone beside this set clearly stores them.
Say I have a class Foo, which contains some kind of container, say a vector<Bar *> bars. I want to allow the user to iterate through this container, but I want to be flexible so that I might change to a different container in the future. I'm used to Java, where I could do this
public class Foo
{
List<Bar> bars; // may change to a different collection
// User would use this
public Iterator<Bar> getIter()
{
return bars.iterator(); // can change without user knowing
}
}
C++ iterators are designed to look like raw C++ pointers. How do I get the equivalent functionality? I could do the following, which returns the beginning and end of the collection as an iterator that the user can walk himself.
class Foo
{
vector<Bar *> bars;
public:
// user would use this
std::pair<vector<Bar *>::iterator , vector<Bar *>::iterator > getIter()
{
return std::make_pair(bars.begin(), bars.end());
}
}
It works, but I feel I must be doing something wrong.
Function declaration exposes the fact that I'm using a vector. If I change the implementation, I need to change the function declaration. Not a huge deal but kind of goes against encapsulation.
Instead of returning a Java-like iterator class that can do its own bounds check, I need to return both the .begin() and .end() of the collection. Seems a bit ugly.
Should I write my own iterator class?
You could adapt the vector behaviour and provide the same interface:
class Foo
{
std::vector<Bar *> bars;
public:
typedef std::vector<Bar*>::iterator iterator;
iterator begin() {
return bars.begin();
}
iterator end() {
return bars.end();
}
};
Use Foo::iterator as the iterator type outside of the container.
However, bear in mind that hiding behind the typedef offers less than it seems. You can swap the internal implementation as long as it provides the same guarantees. For example, if you treat Foo::iterator as a random access iterator, then you cannot swap a vector for a list internally at a later date without a comprehensive refactoring because list iterators are not random access.
You could refer to Scott Meyers Effective STL, Item 2: beware the illusion of container independent code for a comprehensive argument as to why it might be a bad idea to assume that you can change the underlying container at any point in future. One of the more serious points is iterator invalidation. Say you treat your iterators as bi-directional, so that you could swap a vector for a list at some point. An insertion in the middle of a vector will invalidate all of its iterators, while the same does not hold for list. In the end, the implementation details will leak, and trying to hide them might be Sisyphus work...
You are looking for type erasure. Basically you want an iterator with vector erased from it. This is roughly what it looks like:
#include <vector>
#include <memory>
#include <iostream>
template<class T>
class Iterator{ //the class that erases the iterator type
//private stuff that the user should not care about
struct Iterator_base{
virtual void increment() = 0;
virtual T &dereference() = 0;
virtual ~Iterator_base() = default;
};
std::unique_ptr<Iterator_base> iter;
template<class Iter>
class Iterator_helper : public Iterator_base{
void increment() override{
++iter;
}
T &dereference() override{
return *iter;
}
Iter iter;
public:
Iterator_helper(const Iter &iter) : iter(iter){}
};
public:
template<class Iter>
Iterator(const Iter &iter) : iter(new Iterator_helper<Iter>(iter)){}
//iterator functions for the user
Iterator &operator ++(){
iter->increment();
return *this;
}
T &operator *(){
return iter->dereference();
}
};
struct Bar{
Bar(int i) : i(i){};
int i;
};
class Foo
{
std::vector<Bar> bars;
public:
Foo(){ //just so we have some elements to point to
bars.emplace_back(1);
bars.emplace_back(2);
}
// user would use this
Iterator<Bar> begin()
{
return bars.begin();
}
};
int main(){
Foo f;
auto it = f.begin();
std::cout << (*it).i << '\n'; //1
++it; //increment
std::cout << (*it).i << '\n'; //2
(*it).i++; //dereferencing
std::cout << (*it).i << '\n'; //3
}
You can now pass any iterator (actually anything) to Iterator that support pre-increment, dereferencing and copy constuction, completely hiding the vector inside. You can even assign Iterators that have a vector::iterator inside to an Iterator that has a list::iterator inside, though that may not be a good thing.
This is a very bare-bone implementation, you would want to also implement operators ++ for post-increment, --, ->, ==, =, <, >, <=, >=, != and possibly []. Once you are done with that you need to duplicate the code into a Const_Iterator. If you don't want to do that yourself consider using boost::type_erasure.
Also note that you are paying for this encapsulation with unnecessary dynamic memory allocations, cache misses, virtual function calls that probably cannot be inlined and triply redundant code (same functions in Iterator, Iteratr_base and Iterator_helper).
vector is still present in the private part of Foo, you can get rid of that with a pimpl, adding another level of indirection.
I feel like this bit of encapsulation is not worth the cost, but your mileage may vary.