Let's say I have a class:
class NumberCollection
{
public:
typedef std::set<int> SetType;
typedef SetType::iterator iterator;
void insert(int n);
iterator begin();
iterator end();
size_t size() const;
iterator difficultBegin();
iterator difficultEnd();
size_t difficultSize() const;
private:
SetType easySet_, difficultSet_;
}
Where insert() adds an element to easySet_. difficultSet_'s members change depending on the members of easySet_.
The problem I am having is that, multiple insertions means that difficultSet_ is constantly recalculated. So I want difficultSet_ to be calculated lazily (i.e., only when difficultBegin(), difficultEnd(), or difficultSize() are called). The problem is, then I actually have to make difficultSet_ into a mutable because otherwise difficultSize() cannot operate on it.
So now my class declaration looks like
class NumberCollection
{
public:
typedef std::set<int> SetType;
typedef SetType::iterator iterator;
void insert(int n);
iterator begin();
iterator end();
size_t size() const;
iterator difficultBegin();
iterator difficultEnd();
size_t difficultSize() const;
private:
SetType easySet_;
mutable SetType difficultSet_;
mutable bool upToDate_;
}
I feel like this is bad design though. Is there a better way?
That's totally the way to do it. Const can mean binary const, or it can mean conceptually const. Using mutable means you're doing the later, which is fine.
To help understand why to use mutable, we can explore other options.
You can solve the same problem using const_cast:
size_t NumberCollection::difficultSize() const
{
if(!upToDate_)
{
NumberCollection& nonConst = const_cast<NumberCollection&>(*this);
nonConst.difficultSet_ = PerformExpensiveCalculationFunction();
nonConst.upToDate_ = true;
}
// etc....
}
Having offered this solution, I'll say that it's inferior to using mutable. If a member is marked as mutable, then simply by looking at the header I can gather how you are treating it. I don't get this information if you use const_cast.
But then somebody might take the other side of the debate, and say that it's better not to expose implementation details in the header.
This is essentially the reason C++ has the mutable construct. Alan De Smet's rant about the misuse of mutable shows the kinds of situations in which mutable should not be used.
In this case, difficultSize() does not change what the NumberCollection represents - which is suitable for marking as const. It does how ever need to change the internals at times, which is why you need to mark difficultSet_ and upToDate_ as mutable.
Your solution is fine in C++98. Note that in C++11 you should consider to synchronize the access to your mutable data. Otherwise you may run into problems when you your class is used by the STL, which assumes that all const member functions are thread-safe.
For further details, see Does const mean thread-safe in C++11?
Related
Const casting container value-types seems not possible. A comment in the other question suggests iterators as a solution, yet does not go into detail.
Since I seemingly cannot simply convert a container from a non-const to a const version as a function parameter, I arrive at Iterators to maybe be able to do the job.
I actually have a vector<shared_ptr<Thing> > to be treated as const vector<shared_ptr<Thing const> >.
With it I intend to use the shared_ptr<Thing const> as further references in other structures, without allowing those structures to alter the Things. Those structures may create their own objects, stored by their own shared_ptr, if they want slightly different content within their containers, while still actively sharing most Things with other objects.
So I would need either shared_ptr<const Thing>&, or const shared_ptr<const Thing>& from an Iterator through the sequence. Either would suffice, but just because one can be indifferent about passing references in this example, because of shared_ptr's copy semantics are about just that.
Yet even just using default const_iterator, retrieved by cbegin(),c.end() and such, will give me a const shared_ptr<Thing>& instead.
Edit: To copy the vector element for element would be one way technically, as in the other question, yet undesired for interface reasons. I am going for reinterpretation here, not copy.
Any suggestions on where a workaround might lie?
Based on your situation, it sounds like defining a custom iterator with the semantics you want is the safe and simple way to go. It's technically correct, hard to accidentally misuse, and fairly fast, just requiring a shared_ptr copy on iterator dereference.
I always recommend boost::iterator_facade or boost::iterator_adaptor for creating an iterator type. Since it will contain the original vector iterator as a "base" implementation, iterator_adaptor is helpful.
class const_Thing_ptr_iterator :
public boost::iterator_adaptor<
const_Thing_ptr_iterator, // CRTP derived type
std::vector<std::shared_ptr<Thing>>::const_iterator, // base iterator type
std::shared_ptr<const Thing>, // value_type
std::random_access_iterator_tag, // traversal type
std::shared_ptr<const Thing> // reference
>
{
public:
const_Thing_ptr_iterator() = default;
explicit const_Thing_ptr_iterator(base_type iter)
: iterator_adaptor(iter) {}
};
The reference iterator type would be std::shared_ptr<const Thing>& by default, but in this case it can't be a reference since there is no object of that type. Normally the class would define some of the behavior functions like dereference or increment, but none are needed here: the only change from the base vector iterator is to the return type of operator*, and the default reference dereference() const { return *base_reference(); } works fine by implicit conversion from const std::shared_ptr<Thing>& to std::shared_ptr<const Thing>.
The class could also be a template taking Thing as its type parameter, to create multiple iterator types.
Then to provide a container-like view object, we can use C++20's std::ranges::subrange to provide begin() and end() and a few other things helping the out the range templates:
#include <ranges>
class const_Thing_ptrs_view
: public std::ranges::subrange<const_Thing_ptr_iterator>
{
public:
explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
: subrange(const_Thing_ptr_iterator(vec.begin()),
const_Thing_ptr_iterator(vec.end())) {}
};
Or if that's not available, a simple class with begin() and end():
class const_Thing_ptrs_view {
public:
explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
: m_begin(vec.begin()), m_end(vec.end()) {}
const_Thing_ptr_iterator begin() const { return m_begin; }
const_Thing_ptr_iterator end() const { return m_end; }
private:
const_Thing_ptr_iterator m_begin;
const_Thing_ptr_iterator m_end;
};
Demo on godbolt. (Clang doesn't like the ranges code due to this libstdc++ incompatibility; I'm not sure how to get godbolt to switch it to clang's libc++.)
A pretty simple approach might be maintaining a vector of pointers to const already internally – and casting the const away on internal usage.
Warning: Don't consider this as an invitation for doing so carelessly! If you do so, at some point of time you will break something. After all those objects are const for a reason!
In given case, though, this reason is a pure external one – if it wasn't for the public interface the objects wouldn't ever have got const anyway so casting it away again is valid in this very specific case.
class DataOwner
{
public:
std::vector<std::shared_ptr<Thing const>> const& data() const
{
return m_data;
}
void modify()
{
m_data.emplace_back(new Thing());
at(0)->doSomething();
}
// for convenience of the user you might optionally duplicate
// the const interface of `std::vector` here as well
private:
std::vector<std::shared_ptr<Thing const>> m_data;
Thing* at(size_t index)
{
// only one single location where const-casting
// remember: generally a dangerous operation,
// here one of the view valid use cases
return const_cast<Thing*>(m_data[index].get());
// don't forget to document in your own code WHY it is valid here
}
};
Copying around all the shared pointers into new vectors can become pretty expensive quickly, especially if the original source vector gets updated frequently and referring instances thus need to fetch updates again and again.
I personally would thus rather provide either a wrapper around std::vector or around std::shared_ptr that give modifying access only to the data owner (who would be a friend then) while the general interface only allows non-modifying access. Wrapping the vector, though, will require the shared pointers to get copied whenever retrieving one, thus involving reference counting, additionally the solution gets more complex, thus going with the wrapper around the shared pointer here:
struct Thing
{
void doSomething() { }
void doSomethingElse() const { }
};
class DataOwner
{
public:
class SharedPointer
{
public:
SharedPointer(SharedPointer const&) = default;
SharedPointer(SharedPointer&&) = default;
SharedPointer& operator=(SharedPointer const&) = default;
SharedPointer& operator=(SharedPointer&&) = default;
Thing const& operator*() const
{
return *m_data;
}
Thing const* operator->() const
{
return m_data.get();
}
Thing const* get() const
{
return m_data.get();
}
// should be all of the public interface needed...
private:
friend class DataOwner;
SharedPointer(Thing* t) : m_data(t) { }
std::shared_ptr<Thing> m_data;
};
std::vector<SharedPointer> const& data() const
{
return m_data;
}
void modify()
{
m_data.emplace_back(SharedPointer(new Thing()));
m_data[0].m_data->doSomething();
// if needed at many places you might want to have a
// convenience function, see below...
at(0)->doSomething();
}
// for convenience of the user you might optionally duplicate
// the const interface of `std::vector` here as well
private:
std::vector<SharedPointer> m_data;
Thing* at(size_t index)
{
return m_data[index].m_data.get();
}
};
int main()
{
DataOwner o;
o.modify();
o.data()[0]->doSomethingElse();
// o.data()[0]->doSomething(); won't compile!
return 0;
}
When defining my custom containers, it is more easy and very general (for me) making them iterable by just adding size() and []. This is, having the following member methods:
unsigned int size() const { . . . }
T & operator[] (unsigned int pos) { . . . }
In order to benefit from the STL algorithms, I provide an adaptor
from any container class having the above methods to an iterator valid for the STL functions.
With it, I can write easily things like
MyContainer<int, 5> mc;
IteratorFromContainer<MyContainer<int,5>, int> iter (&mc);
int i=1; for (auto & e : iter) { e = i++; }
for (auto e=iter.begin(); e!=iter.end(); ++e) { cout << (*e) << endl; }
int sum = std::accumulate(iter.begin(), iter.end(), 0);
int prod = std::accumulate(iter.begin(), iter.end(), 1, [](int a, int b) {return a*b;});
Surprisingly (to me) my adaptor (template) class works (the above sample code) equally well
with any of the following (1, 2, or 3):
template<typename Container, typename T>
// 1.
class IteratorFromContainer : public std::iterator<input_iterator_tag, T> {
// 2.
class IteratorFromContainer : public std::iterator<output_iterator_tag, T> {
// 3.
class IteratorFromContainer {
Why?. Should not the adaptor derive from std::iterator always?
What kind of iterator (what _tag) should I use, considering that the iterator is based in random access (size() and []) and has output and input capabilities: RandomAccess, ContinguousIterator?
Thanks
C++ uses a thing called duck-typing, which has awesome pros, and some really wierd cons. One of those cons are, if you break the interface contract, it might not notice until much later, if ever. When you use the for-loops, the compiler uses the iterator's copy constructor, operator++(), operator==(...) and operator*(), which you have, so it sometimes may work fine (I'm pretty sure GCC will point out the error in your options #2 & #3 though). I don't know the exact requirements for std::accumulate, but most likely they're similar. And so as long as you have those your code "works".
However, with options #2 and #3, you're breaking the "contract" about what it means to be an iterator. As such, the compiler or library may receive an update, and your code may stop working. The requirements for each iterator type depends on the type of iterator you're modeling, for more details see this page: How to implement an STL-style iterator and avoid common pitfalls?. However, the minimum requirements are you must have these operations:
iterator(const iterator&);
~iterator();
iterator& operator=(const iterator&);
iterator& operator++(); //prefix increment
reference operator*() const;
and also std::iterator_traits<IteratorFromContainer> must have these typedefs:
typedef ???? difference_type; //almost always ptrdif_t
typedef ???? value_type; //almost always T
typedef ???? reference; //almost always T& or const T&
typedef ???? pointer; //almost always T* or const T*
typedef ???? iterator_category; //usually std::forward_iterator_tag or similar
For simplicity, if you don't specialize std::iterator_traits yourself, it will check if your iterator has those typedefs, and if so, it will copy the ones from your iterator. std::iterator has these typedefs, and so if you inherit from it, you automatically have the typedefs, and so std::iterator_traits will automatically have them, so you'll meet the contractual guarantees. But inheriting from std::iterator is not necessary, you can add the typedefs yourself, or specialize iterator_traits.
Since you're working with operator[], it makes sense for you to use std::random_access_iterator_tag, which has many more requirements than those listed above. Again, see my other answer linked above for details on exactly what those members are.
I've read that hiding the actual container type used in a class is a good thing. It makes accidentally (or intentionally) writing code that conforms to the actual container type (that may change and break prior code) harder and thus the code is more robust.
Therefore, that means that this is not a good idea; If the implementation changes from a std::vector to a std::list things will break:
class MyType {
public:
const std::vector<MyType*>& GetChildren() const;
std::vector<MyType*>& GetChildren();
const std::vector<MyType*>::size_type GetChildCount() const;
std::vector<MyType*>::size_type GetChildCount();
private:
MyType* _parent;
std::vector<MyType*> _children;
};
Using a typedef makes it look like it's hidden, but a user only need to look at the mouse-over definition (at least in Visual Studio) to see the actual type and code accordingly. Even worse, the compiler doesn't see the typedef at all, it just sees the underlying type.
class MyType {
public:
typedef std::vector<MyType*> ChildContainer;
typedef ChildContainer::size_type ChildContainerSize;
const ChildContainer& GetChildren() const;
ChildContainer& GetChildren();
const ChildContainerSize GetChildCount() const;
ChildContainerSize GetChildCount();
private:
MyType* _parent;
ChildContainer _children;
};
Question: Is there a way to hide the underlying container type so that it will be difficult for the user to code around it short of wrapping the STL container in a potentially poorly written and not-needed wrapper?
Sometimes you have no choice, but to show the underlying container types. For example, for randomly accessed containers like std::vector v, I can do sort(v.begin(), v.begin()+10); whereas for a std::list li, I can only do li.sort(), how can you hide the container type, and only use iterators? These iterators are totally incompatible. Also, container type is one of the most fundamental consideration in software designing, why should you think about the future possibility of changing container type? That usually means bad designing, and total remake of the software.
You should use typedefs and iterators. Then if you want to change the container, you can because users are only dependent upon the iterators. People can always break it if they want, but you are exposing an easy way to NOT break.
class MyType {
public:
typedef std::vector<MyType*> ChildContainer;
typedef ChildContainer::size_type ChildContainerSize;
typedef std::vector<MyType*> ChildContainerIter;
typedef std::vector<MyType*> ChildContainerCIter;
const ChildContainerCIter GetChildrenBegin() const { return _children.begin(); }
ChildContainerIter GetChildrenBegin() { return _children.begin(); }
const ChildContainerCIter GetChildrenEnd() const { return _children.end(); }
const ChildContainerSize GetChildCount() const;
private:
MyType* _parent;
ChildContainer _children;
};
And no there is not a way to really hide it completely. The compiler needs to know what type it is when their code is compiled.
I have MyClass that hide the container inside it, I want to control when new item is added to the container and when an item is to be deleted from container, but i don't need to control read-only operation such as getter function
class MyClass {
protected:
std::vector<MySubClass> subclasses;
public:
}
for interfacing with the user of MyClass, should I implement interface function such as :
addSubClass(), getSubClassAt(int ), getSubClassIndex(MySubclass ), delSubClass().
or its better just return const iterator, for readonly operation :
std::vector<MySubClass>::const_iterator getSubclassIterator();
and provide special write-operation function such as
addSubClass(), delSubClass().
or is there is a better way than these?
If you invent your own member functions for manipulating the internal list of objects, then I will have to learn your interface when I want to use your class.
I much rather you use the conventions of the standard library, which I already know, so I can use your class immediately:
class MyClass {
protected:
std::vector<MySubClass> subclasses;
public:
typedef std::vector<MySubClass>::const_iterator const_iterator;
const_iterator begin() const {return subClasses.begin();}
const_iterator end () const {return subClasses.end ();}
void insert(const_iterator where, const MySubClass& obj);
iterator erase(iterator pos);
iterator erase(iterator begin, iterator end);
// ...
}
Your users will really appreciate it if you provide the subset of standard library container calls that apply to your container: Stuff like push_back, begin, end, and find for example. If you reinvent the interface it will be harder for clients to understand, and it won't always be compatible with standard algorithsm.
I have a class that contains, among other things, an std::list. I want to expose this list but only in such a way that the structure and the data it contains are read only, but still can be used with iterators.
The way I've got it 'working' atm is to return a copy of the list. This leave my class 'safe' but of course does nothing to stop the caller from modifying their copy of the list and not getting the right data.
Is there a better way?
Why not return a const std::list& instead?
Instead of exposing the list itself (at all) just expose const_iterators to its beginning and end. See cbegin() and cend() for help in doing this...
Return a const reference:
const std::list<T>& getList() const;
or just return const iterators:
std::list<T>::const_iterator getListBegin() const;
std::list<T>::const_iterator getListEnd() const;
There is a dependency issue in exposing one's data member to the outside world.
If you decide to change your attribute for something better (because list are the last resort container), or because you have a new requirements, then all your clients will be impacted, and that is bad.
One simple alternative is to offer a typedef:
typedef std::list<Foo>::const_iterator const_iterator;
IF your clients use your alias, then it's a simple matter of recompiling the code.
Another alternative is to create your own iterator class (not that difficult) which will embed the actual iterator.
class const_iterator
{
public:
private:
typedef std::list<Foo>::const_iterator base_type;
base_type mBase;
};
You simply forward all the operations to the actual iterator, and your clients (though they will have to recompile if you change your container) cannot accidentally use an unaliased type.
Then, the 3rd solution is similar to the first, except that you abstract the type... it's quite inefficient though (for a list), so I would not really advise it: iterators are supposed to be cheap to copy, you don't want to new anything.
class foo {
private:
typedef std::list<bar> bar_cont_t;
public:
typedef bar_const_t::const_iterator bar_const_iterator;
bar_const_iterator bar_begin() const {return bar_data_.begin();}
bar_const_iterator bar_end () const {return bar_data_.end ();}
// whatever else
private:
bar_cont_t bar_data_;
};