I\'ve started coding in C++, coming from a Java background (actually I\'d studied C++ at my university, but we never got to the STL etc.)
Anyway, I\'ve gotten to the
The C++ standard library (note: it's not called the STL) has many existing container types: vector
, array
, deque
, forward_list
, list
, set
, map
, multiset
, multimap
, unordered_set
, unordered_map
, unordered_multiset
, unordered_multimap
, stack
, queue
, priority_queue
. Chances are, you just want to use one of these directly - you certainly never want to derive from them. However, it's certainly possible that you may need to implement your own special container type at some point, and it would be nice if it matched some interface, right?
But no, there aren't some abstract base classes that the containers derive from. However, the C++ standard provides requirements for types (sometimes known as concepts). For example, if you look at section §23.2 of the C++11 standard (or here), you'll find the requirements for a Container. For example, all containers must have a default constructor that creates an empty container in constant time. There are then more specific requirements for Sequence Containers (like std::vector
) and Associative Containers (like std::map
). You can code your classes to meet these requirements and then people can safely use your containers as they would expect to.
Of course, there are requirements for many things other than containers. For example, the standard provides requirements for different types of iterators, random number generators, and so on.
A number of people on the ISO C++ committee (Study Group 8, in fact) are looking into making these concepts a feature of the language. The proposal would allow you to specify requirements for types that need to be met for them to be used as template type arguments. For example, you would be able to write a template function a little like this:
template <Sequence_container C>
void foo(C container); // This will only accept sequence containers
// or even just:
void foo(Sequence_container container);
However, I'm thinking this is currently beyond your understanding of C++.
In C++, collections (aka containers) and generic algorithms that operate on them are implemented in a way that is completely unaware of inheritance. Instead, what connects them are iterators: For each container, specify which category of iterators it provides, for each algorithm, state which category of iterators it works with. So in a way, iterators 'bridge' the other two together and this is how STL affords to keep the number of containers and algorithms to the minimum (N+M instead of N*M). Containers are further defined as sequence containers (vector, deque, list (double linked list), or forward_list (singly linked list) and associative containers (map, set, hashmap, hashset, etc). Sequence containers are concerned with performance (i.e. which one is a better choice for a different situation). Associative containers are concerned with how things get stored in them and its consequence (binary tree vs hashed array). Similar ideas apply for algorithms. This is a gist of generic programming as exemplified by STL by being specifically and intentionally not object oriented. Indeed you would have to distort a pure OO approach to achieve smooth generic programming. Such a paradigm does not ride happily with languages such as Java or Smalltalk
The short answer is: there isn't an equivalent, because C++ does things differently.
There's no point arguing about this, it's just the way things are. If you don't like this, use a different language.
The long answer is: there is an equivalent but it's going to make you a little unhappy, because while Java's model of containers and algorithms is heavily based around inheritance, C++'s isn't. C++'s model is heavily based around generic iterators.
Let's say, to take your example, that you want to implement a set. Ignoring the fact that C++ already has std::set
, std::multiset
, std::unordered_set
and std::unordered_multiset
, and that these are all customisable with different comparators and allocators, and the unordered ones have customisable hash functions, of course.
So let's say you want to reimplement std::set
. Perhaps you're a computer science student and you want to compare AVL trees, 2-3 trees, red-black trees and splay trees, for example.
How would you do this? You would write:
template<class Key, class Compare = std::less<Key>, class Allocator = std::allocator<Key>>
class set {
using key_type = Key;
using value_type = Key;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using key_compare = Compare;
using value_compare = Compare;
using allocator_type = Allocator;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = std::allocator_traits<Allocator>::pointer;
using const_pointer = std::allocator_traits<Allocator>::const_pointer;
using iterator = /* depends on your implementation */;
using const_iterator = /* depends on your implementation */;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>
iterator begin() const;
iterator end() const;
const_iterator cbegin() const;
const_iterator cend() const;
reverse_iterator rbegin() const;
reverse_iterator rend() const;
const_reverse_iterator crbegin() const;
const_reverse_iterator crend() const;
bool empty() const;
size_type size() const;
size_type max_size() const;
void clear();
std::pair<iterator, bool> insert(const value_type& value);
std::pair<iterator, bool> insert(value_type&& value);
iterator insert(const_iterator hint, const value_type& value);
iterator insert(const_iterator hint, value_type&& value);
template <typename InputIterator>
void insert(InputIterator first, InputIterator last);
void insert(std::initializer_list<value_type> ilist);
template <class ...Args>
std::pair<iterator, bool> emplace(Args&&... args);
void erase(iterator pos);
iterator erase(const_iterator pos);
void erase(iterator first, iterator last);
iterator erase(const_iterator first, const_iterator last);
size_type erase(const key_type& key);
void swap(set& other);
size_type count(const Key& key) const;
iterator find(const Key& key);
const_iterator find(const Key& key) const;
std::pair<iterator, iterator> equal_range(const Key& key);
std::pair<const_iterator, const_iterator> equal_range(const Key& key) const;
iterator lower_bound(const Key& key);
const_iterator lower_bound(const Key& key) const;
iterator upper_bound(const Key& key);
const_iterator upper_bound(const Key& key) const;
key_compare key_comp() const;
value_compare value_comp() const;
}; // offtopic: don't forget the ; if you've come from Java!
template<class Key, class Compare, class Alloc>
void swap(set<Key,Compare,Alloc>& lhs,
set<Key,Compare,Alloc>& rhs);
template <class Key, class Compare, class Alloc>
bool operator==(const set<Key,Compare,Alloc>& lhs,
const set<Key,Compare,Alloc>& rhs);
template <class Key, class Compare, class Alloc>
bool operator!=(const set<Key,Compare,Alloc>& lhs,
const set<Key,Compare,Alloc>& rhs);
template <class Key, class Compare, class Alloc>
bool operator<(const set<Key,Compare,Alloc>& lhs,
const set<Key,Compare,Alloc>& rhs);
template <class Key, class Compare, class Alloc>
bool operator<=(const set<Key,Compare,Alloc>& lhs,
const set<Key,Compare,Alloc>& rhs);
template <class Key, class Compare, class Alloc>
bool operator>(const set<Key,Compare,Alloc>& lhs,
const set<Key,Compare,Alloc>& rhs);
template <class Key, class Compare, class Alloc>
bool operator>=(const set<Key,Compare,Alloc>& lhs,
const set<Key,Compare,Alloc>& rhs);
Of course you don't have to write ALL of those, especially if you're just writing something to test parts of them. But if you write all that (and a little bit more I excluded for clarity), then what you will have will be a fully functioning set class. And what is special about that set class?
You can use it anywhere. Anything that works with a std::set
will work with your set. It doesn't have to be programmed specially for it. It doesn't need anything. And anything that works on ANY set type should work on it. And any of Boost's algorithms will work on sets.
And any algorithms you write to use on sets will work on your sets and boost's sets and lots of other sets. But not just on sets. If they're written competently they'll work on any container that supports a particular type of iterator. If they need random access they'll require RandomAccessIterators, which std::vector
provides, but std::list
doesn't. If they need BidirectionalIterators, then std::vector
and std::list
(and others) will work fine, but std::forward_list
won't.
The iterator/algorithm/container thing works really well. Consider the cleanliness of reading a file into a string in C++:
using namespace std;
ifstream file("file.txt");
string file_contents(istreambuf_iterator<char>(file),
istreambuf_iterator<char>{});
You need to try and let go of the Java mindset. You see, the beauty of STL, is that it separates algorithms from containers through iterators.
Long story short: Pass around iterators to your algorithms. Don't inherit.
Here are all the containers: http://en.cppreference.com/w/cpp/container
And here are all the algorithms: http://en.cppreference.com/w/cpp/algorithm
There may be two reasons why you may want to inherit:
To briefly touch upon the first point, if you need to store an array of things (say an array of objects in a game scene), do exactly that, have an array of these objects as a member to the Scene object. There is no need to subclass to fully utilize the container. In other words, prefer composition over inheritance. This has been done to death already, and is accepted in the Java world as doing "The Right Thing". See discussion here, it's in the GoF book! Same thing applies to C++.
Example:
To address the second point let's consider a scenario. You are making a 2D sidescroller game, and you have a Scene
object, with an array of GameObject
s. These GameObjects
have positions, and you'd like to sort them by position, and do binary search to find the closest object, as an example.
In the C++ mindset, the storage of elements and manipulation of containers are two separate things. The container classes provide the bare minimum functionality, for creation/insertion/removal. Anything interesting above that is relegated to Algorithms. And the bridge between them are iterators. The idea is that whether you use std::vector<GameObject>
(equivalent to Java's ArrayList I think), or your own implementation is irrelevant as long as access to elements is the same. Here is a contrived example:
struct GameObject {
float x, y;
// compare just by x position
operator < (GameObject const& other)
{
return x < other.x;
}
};
void example() {
std::vector<GameObject> objects = {
GameObject{8, 2},
GameObject{4, 3},
GameObject{6, 1}
};
std::sort(std::begin(objects), std::end(objects));
auto nearestObject = std::lower_bound(std::begin(objects), std::end(objects), GameObject{5, 12});
// nearestObject should be pointing to GameObject{4,3};
}
Things to note here, the fact that I used std::vector
to store my objects, doesn't matter as much as the fact I can perform random access on its elements. The iterators returned by the vector
capture that. As a result we can sort and perform binary search.
The essence of the vector is random access to elements
We can swap out the vector for any other random access structure, without inheritance, and the code still works perfectly fine:
void example() {
// using a raw array this time.
GameObject objects[] = {
GameObject{8, 2},
GameObject{4, 3},
GameObject{6, 1}
};
std::sort(std::begin(objects), std::end(objects));
auto nearestObject = std::lower_bound(std::begin(objects), std::end(objects), GameObject{5, 12});
// nearestObject should be pointing to GameObject{4,3};
}
For reference, see the functions I have used:
Why is this a valid alternative to inheritance?
This approach gives two orthogonal directions for extensibility:
The standard C++ library already implements lists, maps, sets, etc. There is no point in C++ to implement these data structures again. If you implement something like one of these data structures you'd implement the same concept (i.e., use the same function names, order of parameters, names of nested types, etc.). There are various concepts for container (sequence, associative containers, etc.). More importantly, you'd expose the content of your structure using the appropriate iterator concepts.
Note: C++ isn't Java. Don't try to program Java in C++. If you want to program Java, program Java: it works a lot better than trying to do so in C++. If you want to program C++, program C++.