简体   繁体   English

继承Java集合接口(Set,Map,List等)的C ++等价物是什么? 或者扩展AbstractCollection?

[英]What is the C++ equivalent of inheriting a Java collection interface (Set, Map, List etc.)? Or extending AbstractCollection?

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.) 我已经开始使用C ++进行编码,来自Java背景(实际上我在我的大学学过C ++,但是我们从未进入过STL等)

Anyway, I've gotten to the point where I'm arranging data in all sorts of collections, and I immediately tell myself "Ok, this is a kind of a Set; and this is a List, or an ArrayList; and this is a map etc." 无论如何,我已经到了我在各种集合中安排数据的地步,我立即告诉自己“好吧,这是一种Set;这是一个List或一个ArrayList;这是地图等“ In Java, I would simply have whatever class I'm writing implement the Set or Map or List interface; 在Java中,我只想让我正在编写的任何类实现Set或Map或List接口; but I would probably not go as far as inheriting ArrayList or HashSet or what-not, the implementations there are kind of involved and I wouldn't want to mess them up. 但我可能不会继续继承ArrayList或HashSet,或者什么不是,那里的实现有一些参与,我不想搞砸它们。

Now, what do I do in C++ (with the Standard Library)? 现在,我在C ++中做什么(使用标准库)? There do not seem to be abstract base classes for Sets, Maps, Lists etc - the equivalent of Java interfaces; 似乎没有集合,映射,列表等的抽象基类 - 相当于Java接口; on the other hand, the implementations for the standard containers look pretty horrid. 另一方面,标准容器的实现看起来非常可怕。 Ok, maybe they're not so horrid once you get to know them, but suppose I just wanted to write something like a non-virtual class extending AbstractSet in C++? 好吧,也许他们一旦你了解它们就不那么可怕了,但是假设我只是想写一些类似于在C ++中扩展AbstractSet的非虚拟类? Something I would be able to pass to any function which takes a Set? 我可以传递给任何需要Set的函数吗? How should I go about doing that? 我应该怎么做呢?

Just to clarify - I don't necessarily want to do what's common practice in Java. 只是为了澄清 - 我不一定想做Java中的常见做法。 But, on the other hand, if I have an object which, conceptually, is a kind of set, I want to inherit something appropriate, get default implementations gratis, and be guided by my IDE to implement those methods which I should implement. 但是,另一方面,如果我有一个对象,从概念上讲,它是一种集合,我想继承适当的东西,免费获得默认实现,并由我的IDE指导实现我应该实现的那些方法。

The short answer is: there isn't an equivalent, because C++ does things differently. 简短的回答是:没有等价物,因为C ++以不同的方式做事。

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. 答案很长:有一个等价物,但它会让你有点不高兴,因为虽然Java的容器和算法模型很大程度上是基于继承,但C ++却不是。 C++'s model is heavily based around generic iterators. C ++的模型主要基于泛型迭代器。

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. 忽略C ++已经有std::setstd::multisetstd::unordered_setstd::unordered_multiset这一事实, 并且这些都可以使用不同的比较器和分配器进行自定义,而无序的则具有可自定义的散列函数,当然。

So let's say you want to reimplement std::set . 所以假设你想重新实现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. 也许你是一名计算机科学专业的学生,​​你想要比较AVL树,2-3棵树,红黑树和树枝树。

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. 任何与std::set一起使用的东西都可以使用你的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. Boost的任何算法都可以在集合上运行。

And any algorithms you write to use on sets will work on your sets and boost's sets and lots of other sets. 你编写的用于集合的任何算法都可以在你的集合和boost集合以及许多其他集合上运行。 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. 如果他们需要随机访问,他们将需要RandomAccessIterators, std::vector提供,但std::list不需要。 If they need BidirectionalIterators, then std::vector and std::list (and others) will work fine, but std::forward_list won't. 如果他们需要BidirectionalIterators,那么std::vectorstd::list (以及其他)将正常工作,但std::forward_list不会。

The iterator/algorithm/container thing works really well. 迭代器/算法/容器的功能非常好。 Consider the cleanliness of reading a file into a string in C++: 考虑在C ++中将文件读入字符串的清晰度:

using namespace std;

ifstream file("file.txt");
string file_contents(istreambuf_iterator<char>(file),
                     istreambuf_iterator<char>{});

The standard C++ library already implements lists, maps, sets, etc. There is no point in C++ to implement these data structures again. 标准C ++库已经实现了列表,映射,集合等.C ++中没有必要再次实现这些数据结构。 If you implement something like one of these data structures you'd implement the same concept (ie, 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. 注意:C ++不是Java。 Don't try to program Java in C++. 不要尝试用C ++编写Java。 If you want to program Java, program Java: it works a lot better than trying to do so in C++. 如果你想编写Java,编程Java:它比在C ++中尝试编写它要好得多。 If you want to program C++, program C++. 如果你想编程C ++,编程C ++。

You need to try and let go of the Java mindset. 您需要尝试放弃Java思维模式。 You see, the beauty of STL, is that it separates algorithms from containers through iterators. 你看,STL的优点在于它通过迭代器将算法与容器分开。

Long story short: Pass around iterators to your algorithms. 简而言之:将迭代器传递给算法。 Don't inherit. 不要继承。

Here are all the containers: http://en.cppreference.com/w/cpp/container 以下是所有容器: http//en.cppreference.com/w/cpp/container

And here are all the algorithms: http://en.cppreference.com/w/cpp/algorithm 以下是所有算法: http//en.cppreference.com/w/cpp/algorithm

There may be two reasons why you may want to inherit: 您可能希望继承的原因有两个:

  • You want to reuse implementation (bad idea) 你想重用实现(坏主意)
  • Reuse existing algorithms by making a behavior available (eg inheriting from a base class like AbstractSet) 通过使行为可用来重用现有算法(例如从AbstractSet继承基类)

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. 要简要介绍第一点,如果您需要存储一组数据(比如游戏场景中的一个对象数组),请完全相同,将这些对象的数组作为Scene对象的成员。 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". 这已经完成了死亡,并且在Java世界中被接受为做“正确的事”。 See discussion here , it's in the GoF book! 在这里看到讨论 ,它在GoF书中! Same thing applies to C++. 同样的事情适用于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. 你正在制作一个2D sidescroller游戏,你有一个Scene对象,带有一个GameObject数组。 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. 这些GameObjects有位置,你想按位置对它们进行排序,并做二进制搜索以找到最近的对象,作为一个例子。

In the C++ mindset, the storage of elements and manipulation of containers are two separate things. 在C ++思维模式中,元素的存储和容器的操作是两个不同的东西。 The container classes provide the bare minimum functionality, for creation/insertion/removal. 容器类提供最小的功能,用于创建/插入/删除。 Anything interesting above that is relegated to Algorithms. 以上任何有趣的内容都将归入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 . 我的想法是你是否使用std::vector<GameObject> (相当于我认为的Java的ArrayList),或者你自己的实现是无关紧要的,只要对元素的访问是相同的 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. 这里要注意的事实是,我使用std::vector来存储我的对象,这与我可以对其元素执行随机访问的事实无关。 The iterators returned by the vector capture that. vector捕获返回的迭代器。 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: 这种方法为可扩展性提供了两个正交方向:

  • New containers can be added, without inheritance, just by providing iterator access. 只需提供迭代器访问权限,就可以添加新容器而无需继承。 All existing algorithms work . 所有现有算法都有效
  • New algorithms can be added. 可以添加新算法。 All containers supporting these iterators will work with these new algorithms, past, present or future. 支持这些迭代器的所有容器都将使用这些新算法,包括过去,现在或将来。

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 . C ++标准库(注意:它不称为STL)有许多现有的容器类型: vectorarraydequeforward_listlistsetmapmultisetmultimapunordered_setunordered_mapunordered_multisetunordered_multimapstackqueuepriority_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 ). 但是,C ++标准提供了对类型的要求 (有时称为概念 )。 For example, if you look at section §23.2 of the C++11 standard (or here ), you'll find the requirements for a Container. 例如,如果您查看C ++ 11标准(或此处 )的第23.2节,您将找到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 ). 然后对Sequence Containers (如std::vector )和Associative Containers (如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. ISO C ++委员会(实际上是第8研究组)的许多人正在研究将这些概念作为该语言的一个特征。 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++. 但是,我认为这目前超出了你对C ++的理解。

In C++, collections (aka containers) and generic algorithms that operate on them are implemented in a way that is completely unaware of inheritance. 在C ++中,集合(也称为容器)和对它们进行操作的通用算法是以完全不知道继承的方式实现的。 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). 所以在某种程度上,迭代器将另外两个连接在一起,这就是STL如何将容器和算法的数量保持在最小值(N + M而不是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 (ie 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 容器进一步定义为序列容器(vector,deque,list(双链表)或forward_list(单链表)和关联容器(map,set,hashmap,hashset等)。序列容器与性能有关(即哪个对于不同的情况,一个是更好的选择。关联容器关注事物如何存储在它们中及其结果(二叉树与散列数组)。类似的想法适用于算法。这是通用编程的要点,如STL所示事实上,你必须扭曲一个纯粹的OO方法来实现平滑的泛型编程。这样的范例不能与Java或Smalltalk等语言愉快地搭配

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM