Using STL containers with a class containing its own key - c++

I have an object, which can be identified by its name, and I want to place it in one of the STL containers.
class MyClass {
public:
//getters and setters, other functions
private:
std::string name;
//other member variables
};
So at first I thought that the usage of map-like structures is irrelevant in my case, because in those structures the identifier (the key) is separated from the class itself. Using a map I must return the name variable and copy it "outside" the class (waste of memory and illogical, breaking OOP rules).
My next shot was the usage of set-like structures. In this case I only have the key field, where I load my whole object. Using this method I must overload my <, > and == operators in order to use an object as key. I can even make a functor for hashing if I use unordered_set, it works just fine. The problem here is that I cannot use the container functions as I would with a map. This is valid mapInstance.find("example"), and this is not setInstance.find("example"). I must create an object with the member variable name set to "example" and pass it to the find() function. The problem with this solution is that the other member variables in my class are duplicated and not used. I even tried overloading the <, > and == operators for std::string and MyClass classes, which works fine if I use them like this stringInstance < MyClassInstance, but the container functions are unusable (I even tried to overload the functor to work with a string with no success).
Can you suggest me a simple way (or a way), how to solve this problem with std::set or std::map(maybe others)?
In std::map the key cannot be a reference (as I know), and I don't know how to resolve it with std::set.
Note: The problem with storing a pointer in a map's key field is that if we change our mind and use unordered_map instead of map, the hash will be calculated based on the pointer, not based on the string (the hash function can be overridden, but it seems very complicated for a simple task).
Thanks for your help!

You should ask yourself what your requirements for your container are.
Things to consider are:
How many objects will usually be in the container
What are the memory constraints
How often will objects be searched for and which complexity is acceptable?
A std::map has some requirements which might conflict with your class. E.g. the key is not allowed to be changed once an element is added to the map. However, your class might change the name at every time. From this consideration it should become clear that a std::map cannot work with a reference to a string as the key.
In the simplest case, you might consider using a std::list and std::find_if with a predicate to check for a special name. This would have O(n) complexity.

Sometimes you have to build upon what's given to get the desired results. I'd suggest that what you're looking for is a specialized adapter for your use case.
Here's a very basic implementation that you can build upon. In many cases, the cache locality you get from using std::vector will give better performance than using a standard container that provides a similar feature set.
Given a simple type and some helper operators:
struct Obj
{
int key;
std::string name;
};
bool operator<(const Obj& lhs, const Obj& rhs)
{
return lhs.key < rhs.key;
}
bool operator==(const Obj& lhs, int rhs)
{
return lhs.key == rhs;
}
bool operator<(const Obj& lhs, int rhs)
{
return lhs.key < rhs;
}
We can devise a simple flat_map class that provides the basic complexity guarantees of a map, but will meet your requirement of finding objects by key without constructing a value type (like you need to with a set). Insertion gives more complexity, but lookups are of similar complexity. If lookups occur much more frequently then insertions (which is the case most of the time) this can work well.
class flat_map
{
public:
using container_type = std::vector<Obj>;
// insert object into the set
// complexity varies based on length of container
void insert(Obj&& obj)
{
container_.emplace_back(std::move(obj));
std::sort(container_.begin(), container_.end());
}
// find with O(log N) complexity
container_type::iterator find(int key)
{
auto it = std::lower_bound(container_.begin(), container_.end(), key);
if(it != container_.end() && *it == key)
return it;
return container_.end();
}
private:
container_type container_;
};
Example usage:
int main()
{
flat_map obj;
obj.insert({1, "one"});
obj.insert({2, "two"});
obj.insert({3, "three"});
auto it = obj.find(2);
std::cout << it->key << ' ' << it->name << '\n';
}
You can extend the flat_map adapter in any way you see fit. Adding the required overloads to meet your requirements, templatizing the parameters (allocator, comparisons, storage types, etc.).

Related

Overloading push_back() in vector to allow non-duplicate elements

Can we overload the push_back() method in std::vector to allow non-duplicate elements? I know std::set and std::unordered_set are supposed to avoid duplicate elements, but std::set sorts the elements and std::unordered_set stores the elements in no particular order. I need to retrieve the elements in the order they are inserted, while ensuring duplicate elements are not inserted.
Edit: There's a possible duplicate for this question here. The best solution to this duplicate proposes to have an auxiliary data structure and another custom method "add". This doesn't look good for me since(I'll put it in a separate documentation) the users inserting data in std::vector rarely refer to the documentation for any custom functions. If there's no efficient way though, this can be a last resort.
Many people advise against it, but it seems there's some kind of urban legend going around that doing so will cause the universe to undergo vacuum decay and reality as we know it will dissolve.
You can publicly inherit from std::vector. But you have to think about what you can do with that.
If you inherit from vector, it is highly recommended that you don't add any data members to it. This can cause object slicing (google "c++ object slicing".) You also need to keep in mind that vector is not using virtual functions. That means you cannot override member functions. You can only shadow them, so it's not guaranteed that it will always be your push_back() function that gets called. The original will get called if you pass an object of your class to something that takes a reference to a vector, for example.
So in the end, you'd need to add a push_back_unique() function instead. But that in turns means that can be served by a simple free function instead. So inheriting vector isn't needed. This of course means there's never a guarantee that the elements in the vector will be unique. Other code might use push_back() instead somewhere.
Inheriting vector makes sense if you want to add completely new convenience functions that don't impose or lift any restrictions that vector has. If you want something that looks like a vector but really isn't (because it has different behavior and/or restrictions), you should implement your own type that delegates the container functionality to vector by either inheriting privately from it, or by having it as a private data member, and then replicate the vector API through public wrapper functions.
But this is very tedious to implement. Usually, you don't really need all the API from vector. So I'd say just write a smaller class around vector that only provides the functionality you need. And that functionality sounds like it's going to be pretty much read-only, since allowing write access to the elements allows for setting an element to the same value as another, breaking the container's uniqueness. So you could do something like:
template<typename T>
class UniqueVector
{
public:
void push_back(T&& elem)
{
if (std::find(vec_.begin(), vec_.end(), elem) == vec_.end()) {
vec_.push_back(std::forward(elem));
}
}
const T& operator[](size_t index) const
{
return vec_[index];
}
auto begin() const
{
return vec_.cbegin();
}
auto end() const
{
return vec_.cend();
}
private:
std::vector<T> vec_;
};
If you still want to allow write access to individual elements, then you can provide non-const functions that check if the value that is passed is already in the vector. Like:
void assign_if_unique(size_t index, T&& value)
{
if (std::find(vec_.begin(), vec_.end(), value) == vec_.end()) {
vec_[index] = std::forward(value);
}
}
This is a minimal example. You should obviously add the functions you actually want. Like size(), empty(), and whatever else you need.
You should first define a free function1 to implement your feature:
template<class T>
std::vector<T>&
push_back_unique(std::vector<T>& dest, T const& src)
{ /* ... */ }
If you use this a lot, and if make sense regarding your program, you might want to define an operator to do so:
template<class T>
std::vector<T>& operator<<(std::vector<T>& dest, T const& src)
{ return push_back_unique(dest, src); }
This allows:
std::vector<int> data;
data << 5 << 8 << 13 << 5 << 21;
for (auto n : data) std::cout << n << " "; // prints 5 8 13 21
1) This is because inheriting from standard containers is often bad practice and brings pitfalls.

Is it possible to use elements of a different type than contained in a std::set to perform search and deletion?

Let's say I have the following:
struct MetadataThingy {
void *actual_thingy;
int some_metadata;
int more_metadata;
bool operator<(MetadataThingy const& other) const {
return actual_thingy < other.actual_thingy;
}
};
where actual_thingy points to some data of importance and I want the container ordered by the value of actual_thingy rather than the value of the element pointed at, but I need to store some other data about it, so I created the wrapper class MetadataThingy with a comparator that only considers the value of the actual_thingy pointer (rather than using a container of void *)
Now, given the following code:
std::set<MetadataThingy> thingy_set;
void test() {
MetadataThingy m1 { nullptr, 5, 20 };
MetadataThingy m2 { &m1, 1, 2 };
MetadataThingy m3 { &m2, 6, 0 };
thingy_set.insert(m1);
thingy_set.insert(m2);
thingy_set.insert(m3);
MetadataThingy m;
m = *thingy_set.find(m2); // OK.
m = *thingy_set.find(static_cast<void *>(&m2)); // Nope. Can't use a pointer.
}
Since each MetadataThingy can be uniquely identified by the pointer value it stores and is ordered by the pointer value, it would make sense to find/delete objects simply by using a void * as the key. As it currently stands, though, I would have to create a dummy MetadataThingy each time I search for an element, which feels really kludgy. I've already considered using just a map with pointers as key and MetadataThingy as value but since each MetadataThingy must also contain the pointer anyway, this feels a bit redundant. So, is there a way to use an element of a type other than that stored in a set to find or delete values in the set, given that elements of the two types are mutually comparable and that elements of one type can be uniquely mapped into the other ( void * and MetadataThingy are isomorphic)? (I didn't include any in the above code, but suppose there are operator overloads for comparing void * and MetadataThingy in any order.)
A little background on the problem I'm trying to solve, just in case anyone can recommend a better approach: I need to order a collection by multiple criteria, so I have several MetadataThingy containers, all sorted by different criteria. "Metadata" in this case would be stuff I need to track the positions of the elements in all containers so that I can do fast removal. This would sound like a perfect job for boost multi-index containers, but the ordering of these elements is constantly changing, which AFAIK would mean it won't work.
As of C++14, std::set has templated versions of its lookup functions find, lower_bound, etc. They allow you to pass any object for comparison, as long as the comparer supports it.
This means you can directly pass your void* to find, as long as the comparer supports comparing MetadataThingy and void*.
For more information, see http://en.cppreference.com/w/cpp/container/set/find.
To understand the limitation regarding Compare::is_transparent, I found this StackOverflow question very helpful.
You can do this by using std::find_if and providing a predicate functor.
#include <algorithm>
struct Predicate
{
void const * const ptr_;
explicit Predicate(const void* ptr) : ptr_(ptr) {}
bool operator()(const MetadataThingy& other)
{
return ptr_ == other.actual_thingy;
}
};
m = *std::find_if(thingy_set.begin(), thingy_set.end(), Predicate(&m2));
You can use the iterator returned by std::find_if to remove the element from the set by passing it to set::erase.
No, the signature of map<>::find requires you pass in the key type.
There is, however, a relatively simple workaround. Use boost::optional or std::tr2::optional (from C++1y) to store your non-key data.
struct MetadataThingy {
void* pBlah;
optional<rest_of_stuff> rest;
static MetadataThingy searcher( void* );
MetadataThingy(...);
};
then call MeatadataThingy::searcher to generate your key value.
Another approach would be to store smart (unique probably) pointers to sub-interfaces, each of which has a "get full data" method. Then, when you want to do a search, create a stub sub-interface that returns nullptr on "get full data".
struct MetadataFull;
struct MetadataRoot {
virtual MetadataFull* get() = 0;
virtual MetadataFull const* get() const = 0;
virtual ~MetadataRoot() {}
};
template<typename T>
struct MetadataFinal: virtual MetadataRoot {
static_assert( std::is_base_of< T, MetadataFinal<T> >::value, "CRTP failure" );
virtual MetadataFull* get() { return static_cast<T*>(this); }
virtual MetadataFull const* get() const { return static_cast<T const*>(this); }
};
struct MetadataStub: virtual MetadataRoot {
virtual MetadataFull* get() { return nullptr; }
virtual MetadataFull const* get() const { return nullptr; }
};
struct MetaDataA: virtual MetaDataRoot {
void* pBlah;
};
struct MetaDataFull: MetaDataA, MetadataFinal<MetaDataFull> {
// unsorted data
};
struct MetaDataAStub: MetaDataA, MetaDataStub {};
now, this can be done with virtual functions but not virtual inheritance with a bit of finagling if you really need it.
The library does not support the behavior that you ask for, although I have seen other people request the same thing (i.e. providing a templated member function find in ordered associative containers that would use a cross comparator), although this is infrequent.
Your type is unusual in that only one of the member attributes takes part on the value of the object (i.e. is used in the comparison). The compiler cannot know that only some of the members (or which of the members) are part of the value and which are not. although it might be that the objects are not really comparable and you just hammered operator< as a simple way of enabling the use in associative containers.
If that is the case, consider dropping the operator< that does not really compare the MetaThingy objects and also change the data structure to be a std::map<void*,MetaThingy>, which would make the design cleaner at the cost of an extra void* per stored object --it might also be the case that the void* is inside the MetaThingy for the lookup in the set... in which case it might even make more sense and you could provide std::map<void*,MetaInfo>.

Why sort() function causes compilation error when it is used for set of class objects

I tried to overload < operator for class and called the function as follows:
bool Edge::operator<(Edge const & e) const {
return this->GetCost() < e.GetCost();
}
in main()
sort(edge_set.begin(),edge_set.end());
In addition, I also tried to write a simple comparator function for the objects, defined in main.cpp and tried to invoke sort(), however failed again:
bool edge_comparator(Edge& e1, Edge& e2){
return (e1.GetCost() < e2.GetCost());
}
in main()
sort(edge_set.begin(),edge_set.end(), edge_comparator);
I get a compilation error for those what I tried. What am I doing wrong here? How can I sort the set of objects?
std::set is a sorted associative container, so it cannot be re-sorted. The sorting criterion is applied on construction and on element insertion.
Edit: You have a set of Edge pointers. If you want this to be sorted according to your own criteria, you can instantiate an std::set with the type of a functor that performs a less-than comparison between a pair of Edge pointers as second template argument:
struct EdgePtrCmp
{
bool operator()(const Edge* lhs, const Edge* rhs) const
{
return lhs->GetCost() < rhs->GetCost();
}
}
then
std::set<Edge*, EdgePtrCmp> s;
Edit 2: The question has been changed again, so it is not clear whether it deals with a set of pointers or not.
Two problems. First, you cannot reorder the elements of a set. Their ordering criteria is determined upon construction, it is a fundamental part of the object. This is necessary in order for it to achieve O(log n) lookups, insertions, and deletions, which is part of the promises of std::set. By default, it will use std::less<Edge>, which should call your operator<. But you could also use your edge_comparator function, like this:
std::set<Edge, bool(*)(Edge&,Edge&)> edge_set(edge_comparator);
Second, std::sort can only be used on random access iterators or better, and std::set iterators are bi-directional.

Programming a simple object oriented graph in C++

I am really trying to be a better programmer, and to make more modular, organized code.
As an exercise, I was trying to make a very simple Graph class in C++ with STL. In the code below, my Node object does not compile because the commented line results in a reference to a reference in STL.
#include <set>
class KeyComparable
{
public:
int key;
};
bool operator <(const KeyComparable & lhs, const KeyComparable & rhs)
{
return lhs.key < rhs.key;
}
class Node : public KeyComparable
{
public:
// the following line prevents compilation
// std::set<Node &> adjacent;
};
I would like to store the edges in a set (by key) because it allows fast removal of edges by key. If I were to store list<Node*>, that would work fine, but it wouldn't allow fast deletion by key.
If I use std::set<Node>, changes made through an edge will only change the local copy (not actually the adjacent Node). If I use std::set<Node*>, I don't believe the < operator will work because it will operate on the pointers themselves, and not the memory they index.
I considered wrapping references or pointers in another class, possibly my KeyComparable class (according to the linked page, this is how boost handles it).
Alternatively, I could store the std::list<Node*> and a std::map<int, iterator>' of locations in thestd::list`. I'm not sure if the iterators will stay valid as I change the list.
Ages ago, everything here would just be pointers and I'd handle all the data structures manually. But I'd really like to stop programming C-style in every language I use, and actually become a good programmer.
What do you think is the best way to handle this problem? Thanks a lot.
As you have deduced, you can't store references in STL containers because one of the requirements of items stored is that they be assignable. It's same reason why you can't store arrays in STL containers. You also can't overload operators without at least one being a user-defined type, which makes it appear that you can't do custom comparisons if you store pointers in an STL class...
However, you can still use std::set with pointers if you give set a custom comparer functor:
struct NodePtrCompare {
bool operator()(const Node* left, const Node* right) const {
return left->key < right->key;
}
};
std::set<Node*, NodePtrCompare> adjacent;
And you still get fast removals by key like you want.

Expose public view of privately scoped Boost.BiMap iterator

I have a privately scoped Boost.BiMap in a class, and I would like to export a public view of part of this map. I have two questions about the following code:
class Object {
typedef bimap<
unordered_set_of<Point>,
unordered_multiset_of<Value>
> PointMap;
PointMap point_map;
public:
??? GetPoints(Value v) {
...
}
The first question is if my method of iteration to get the Point's associated with a Value is correct. Below is the code I'm using to iterate over the points. My question is if I am iterating correctly because I found that I had to include the it->first == value condition, and wasn't sure if this was required given a better interface that I may not know about.
PointMap::right_const_iterator it;
it = point_map.right.find(value);
while (it != point_map.right.end() && it->first == val) {
/* do stuff */
}
The second question is what is the best way to provide a public view of the GetPoints (the ??? return type above) without exposing the bimap iterator because it seems that the caller would have to know about point_map.right.end(). Any efficient structure such as a list of references or a set would work, but I'm a bit lost on how to create the collection.
Thanks!
The first question:
Since you are using the unordered_multiset_of collection type for the right side of your bimap type, it means that it will have an interface compatible with std::unordered_multimap. std::unordered_multimap has the member function equal_range(const Key& key) which returns a std::pair of iterators, one pointing to the first element that has the desired key and one that points to one past the end of the range of elements that have the same key. Using that you can iterate over the range with the matching key without comparing the key to the value in the iteration condition.
See http://www.boost.org/doc/libs/1_41_0/libs/bimap/doc/html/boost_bimap/the_tutorial/controlling_collection_types.html and http://en.cppreference.com/w/cpp/container/unordered_multimap/equal_range for references.
The second question:
Constructing a list or other actual container of pointers or references to the elements with the matching values and returning that is inefficient since it's always going to require O(n) space, whereas just letting the user iterate over the range in the original bimap only requires returning two iterators, which only require O(1) memory.
You can either write a member function that returns the iterators directly, e.g.
typedef PointMap::right_const_iterator match_iterator;
std::pair<match_iterator, match_iterator> GetPoints(Value v) {
return point_map.right.equal_range(v);
}
or you can write a proxy class that presents a container-like interface by having begin() and end() member functions returning those two iterators, and have your GetPoints() member function return an object of that type:
class MatchList {
typedef PointMap::right_const_iterator iterator;
std::pair<iterator, iterator> m_iters;
public:
MatchList(std::pair<iterator, iterator> const& p) : m_iters(p) {}
MatchList(MatchList const&) = delete;
MatchList(MatchList&&) = delete;
MatchList& operator=(MatchList const&) = delete;
iterator begin() { return m_iters.first; }
iterator end() { return m_iters.second; }
};
It's a good idea to make it uncopyable, unmovable and unassignable (like I've done above by deleting the relevant member functions) since the user may otherwise keep a copy of the proxy class and try to access it later when the iterators could be invalidated.
The first way means writing less code, the second means presenting a more common interface to the user (and allows for hiding more stuff in the proxy class if you need to modify the implementation later).