简体   繁体   English

与Java相比,C ++ STL数据结构

[英]C++ STL datastructures compared to Java

I'm currently learning C++ and trying to get used to the standard data structures that come with it, but they all seem very bare. 我目前正在学习C ++并试图习惯它附带的标准数据结构,但它们看起来都非常简单。 For example, list doesn't have simple accessors like get(index) that I'm used to in Java. 例如,list没有像我在Java中习惯的get(index)这样的简单访问器。 Methods like pop_back and pop_front don't return the object in the list either. pop_back和pop_front等方法也不会返回列表中的对象。 So you have to do something like: 所以你必须做以下事情:

Object blah = myList.back();
myList.pop_back();

Instead of something simple like: Object blah = myList.pop_back(); 而不是简单的东西: Object blah = myList.pop_back();

In Java, just about every data structure returns the object back so you don't have to make these extra calls. 在Java中,几乎每个数据结构都返回对象,因此您不必进行额外的调用。 Why is the STL containers for C++ designed like this? 为什么C ++的STL容器设计如此? Are common operations like this that I do in Java not so common for C++? 像Java这样的常见操作在C ++中不常见吗?

edit: Sorry, I guess my question was worded very poorly to get all these downvotes, but surely somebody could have edited it. 编辑:对不起,我想我的问题措辞很差,以获得所有这些downvotes,但肯定有人可以编辑它。 To clarify, I'm wondering why the STL data structures are created like this in comparison to Java. 为了澄清,我想知道为什么与Java相比,这样创建STL数据结构。 Or am I using the wrong set of data structures to begin with? 或者我开始使用错误的数据结构集? My point is that these seem like common operations you might use on (in my example) a list and surely everybody does not want to write their own implementation each time. 我的观点是,这些似乎是您可能在(在我的示例中)列表中使用的常见操作,当然每个人都不想每次都编写自己的实现。

edit: reworded the question to be more clear. 编辑:重写问题要更清楚。

Quite a few have already answered the specific points you raised, so I'll try to take a look for a second at the larger picture. 很多人已经回答了你提出的具体问题,所以我会试着从更大的角度来看一下。

One of the must fundamental differences between Java and C++ is that C++ works primarily with values, while Java works primarily with references. Java和C ++之间必须存在的根本区别之一是C ++主要使用值,而Java主要使用引用。

For example, if I have something like: 例如,如果我有类似的东西:

class X {
    // ...
};

// ...
X x;

In Java, x is only a reference to an object of type X. To have an actual object of type X for it to refer to, I normally have something like: X x = new X; 在Java中, x仅是类型X的一个目的是有类型的实际对象的引用 X为它是指,我通常有类似: X x = new X; . In C++, however, X x; 但是,在C ++中, X x; , by itself, defines an object of type X , not just a reference to an object. ,它本身定义了一个X类型的对象,而不仅仅是对象的引用。 We can use that object directly, not via a reference (ie, a pointer in disguise). 我们可以直接使用该对象,而不是通过引用(即伪装的指针)。

Although this may initially seem like a fairly trivial difference, the effects are substantial and pervasive. 虽然这最初看起来似乎是一个相当微不足道的差异,但影响是巨大的和普遍的。 One effect (probably the most important in this case) is that in Java, returning a value does not involve copying the object itself at all. 一种效果(在这种情况下可能是最重要的)是在Java中,返回值根本涉及复制对象本身。 It just involves copying a reference to the value. 它只涉及复制对值的引用。 This is normally presumed to be extremely inexpensive and (probably more importantly) completely safe -- it can never throw an exception. 这通常被认为是非常便宜的(可能更重要的是)完全安全 - 它永远不会抛出异常。

In C++, you're dealing directly with values instead. 在C ++中,您将直接处理值。 When you return an object, you're not just returning a reference to the existing object, you're returning that object's value, usually in the form of a copy of that object's state. 当您返回一个对象时,您不仅仅是返回对现有对象的引用,而是返回该对象的值,通常是该对象状态的副本。 Of course, it's also possible to return a reference (or pointer) if you want, but to make that happen, you have to make it explicit. 当然,如果你愿意,也可以返回一个引用(或指针),但为了实现这一点,你必须明确它。

The standard containers are (if anything) even more heavily oriented toward working with values rather than references. 标准容器(如果有的话)更倾向于使用值而不是引用。 When you add a value to a collection, what gets added is a copy of the value you passed, and when you get something back out, you get a copy of the value that was in the container itself. 向集合中添加值时,添加的内容是您传递的值的副本,当您返回某些内容时,您将获得容器本身值的副本。

Among other things, this means that while returning a value might be cheap and safe just like in Java, it can also be expensive and/or throw an exception. 除此之外,这意味着虽然返回值可能像Java一样便宜且安全,但它也可能很昂贵和/或抛出异常。 If the programmer wants to store pointers, s/he can certainly do so -- but the language doesn't require it like Java does. 如果程序员想要存储指针,他/她当然可以这样做 - 但语言并不像Java那样需要它。 Since returning an object can be expensive and/or throw, the containers in the standard library are generally built around ensuring they can work reasonably well if copying is expensive, and (more importantly) work correctly, even when/if copying throws an exception. 由于返回一个对象可能很昂贵和/或抛出,标准库中的容器通常是围绕确保它们可以合理地工作,如果复制是昂贵的,并且(更重要的是)正常工作,即使在/如果复制引发异常时也是如此。

This basic difference in design accounts not only for the differences you've pointed out, but quite a few more as well. 这种设计的基本差异不仅考虑了你所指出的差异,还考虑了其​​中的一些差异。

back() returns a reference to the final element of the vector, which makes it nearly free to call. back()返回对向量的最后一个元素的引用,这使得它几乎可以自由调用。 pop_back() calls the destructor of the final element of the vector. pop_back()调用向量的最后一个元素的析构函数。

So clearly pop_back() cannot return a reference to an element that is destroyed. 很明显, pop_back()不能返回对被销毁元素的引用。 So for your syntax to work, pop_back() would have to return a copy of the element before it is destroyed. 因此,为了使您的语法有效, pop_back()必须在元素被销毁之前返回该元素的副本。

Now, in the case where you do not want that copy, we just needlessly made a copy. 现在,在你不想要那份副本的情况下,我们只是不必要地复制了一份。

The goal of C++ standard containers is to give you nearly bare-metal performance wrapped up in nice, easy to use dressing. C ++标准容器的目标是为您提供几乎裸露的金属性能,包裹在漂亮,易于使用的敷料中。 For the most part, they do NOT sacrifice performance for ease of use -- and a pop_back() that returned a copy of the last element would be sacrificing performance for ease of use. 在大多数情况下,它们不会牺牲性能以方便使用 - 而返回最后一个元素副本的pop_back()会牺牲性能以方便使用。

There could be a pop-and-get-back method, but it would duplicate other functionality. 可能有一个pop-and-get-back方法,但它会复制其他功能。 And it would be less efficient in many cases than back-and-pop. 在许多情况下,它的效率低于反叛。

As a concrete example, 作为一个具体的例子,

vector<foo> vec; // with some data in it
foo f = std::move( vec.back() ); // tells the compiler that the copy in vec is going away
vec.pop_back(); // removes the last element

note that the move had to be done before the element was destroyed to avoid creating an extra temporary copy... pop_back_and_get_value() would have to destroy the element before it returned, and the assignment would happen after it returned, which is wasteful. 请注意,必须在元素被销毁之前完成移动以避免创建额外的临时副本... pop_back_and_get_value()必须在返回之前销毁该元素,并且在返回之后将进行分配,这是浪费的。

A list doesn't have a get(index) method because accessing a linked list by index is very inefficient. 列表没有get(index)方法,因为按索引访问链表是非常低效的。 The STL has a philosophy of only providing methods that can be implemented somewhat efficiently. STL的理念是只提供可以有效实施的方法。 If you want to access a list by index in spite of the inefficiency, it's easy to implement yourself. 如果您想要通过索引访问列表,尽管效率低下,那么您自己很容易实现。

The reason that pop_back doesn't return a copy is that the copy constructor of the return value will be called after the function returns (excluding RVO/NRVO). pop_back不返回副本的原因是函数返回后将调用返回值的复制构造函数(不包括RVO / NRVO)。 If this copy constructor throws an exception, you have removed the item from the list without properly returning a copy. 如果此复制构造函数抛出异常,则表示您已从列表中删除该项,而未正确返回副本。 This means that the method would not be exception-safe. 这意味着该方法不会是异常安全的。 By separating the two operations, the STL encourages programming in an exception-safe manner. 通过分离这两个操作,STL鼓励以异常安全的方式进行编程。

Why is the STL containers for C++ designed like this? 为什么C ++的STL容器设计如此?

I think Bjarne Stroustrup put it best : 我认为Bjarne Stroustrup 说得最好

C++ is lean and mean. C ++是精益和卑鄙的。 The underlying principle is that you don't pay for what you don't use. 基本原则是你不支付你不使用的东西。

In the case of a pop() method that would return the item, consider that in order to both remove the item and return it, that item could not be returned by reference. 如果pop()方法将返回该项,请考虑为了同时删除该项并将其返回,该项无法通过引用返回。 The referent no longer exists because it was just pop() ed. 引用不再存在,因为它只是pop() ed。 It could be returned by pointer, but only if you make a new copy of the original, and that's wasteful. 它可以通过指针返回,但只有当你制作原件的new副本时,这才是浪费。 So it would most likely be returned by value which has the potential to make a deep copy. 因此很可能会返回值有可能进行深层复制的值。 In many cases it won't make a deep copy (through copy elision), and in other cases that deep copy would be trivial. 在许多情况下,它不会进行深层复制(通过复制省略),而在其他情况下,深层复制将是微不足道的。 But in some cases, such as large buffers, that copy could be extremely expensive and in a few, such as resource locks, it might even be impossible. 但在某些情况下,例如大缓冲区,这种复制可能非常昂贵,而且在一些情况下,例如资源锁,甚至可能是不可能的。

C++ is intended to be general-purpose, and it is intended to be fast as possible. C ++旨在用于通用目的,并且旨在尽可能快。 General-purpose doesn't necessarily mean "easy to use for simple use cases" but "an appropriate platform for the widest range of applications." 通用并不一定意味着“易于用于简单的用例”,而是“适用于最广泛应用的适当平台”。

Concerning pop() -like functions, there are two things (at least) to consider: 关于类似pop()的函数,有两件事(至少)需要考虑:

1) There is no clear and safe action for a returning pop_back() or pop_front() for cases when there is nothing there to return. 1)如果没有任何内容可以返回,则返回pop_back()pop_front()没有明确安全的操作。

2) These functions would return by value. 2)这些函数将按值返回。 If there were an exception thrown in the copy constructor of the type stored in the container, the item would be removed from the container and lost. 如果在容器中存储的类型的复制构造函数中抛出异常,则该项将从容器中删除并丢失。 I guess this was deemed to be undesirable and unsafe. 我想这被认为是不可取的和不安全的。

Concerning access to list, it is a general design principle of the standard library not to avoid providing inefficient operations. 关于访问列表,标准库的一般设计原则是不避免提供低效的操作。 std::list is a double-linked list, and accessing a list element by index means traversing the list from the beginning or end until you get to the desired position. std::list是一个双链表,并且通过索引访问列表元素意味着从开头或结尾遍历列表,直到到达所需位置。 If you want to do this, you can provide your own helper function. 如果您想这样做,您可以提供自己的帮助功能。 But if you need random access to elements, then you should probably use a structure other than a list. 但是,如果您需要随机访问元素,那么您应该使用列表以外的结构。

list doesn't even have simple accessors like get(index) list甚至没有像get(index)这样的简单访问器

Why should it? 为什么要这样? A method that lets you access the n-th element from the list would hide the complexity of O(n) of the operation, and that's the reason C++ doesn't offer it. 允许您从list访问第n个元素的方法将隐藏操作的O(n)的复杂性,这就是C ++不提供它的原因。 For the same reason, C++'s std::vector doesn't offer a pop_front() function, since that one would also be O(N) in the size of the vector. 出于同样的原因,C ++的std::vector不提供pop_front()函数,因为那个函数也可以是向量大小的O(N)

Methods like pop_back and pop_front don't return the object in the list either. pop_back和pop_front等方法也不会返回列表中的对象。

The reason is exception safety. 原因是异常安全。 Also, since C++ has free functions, it's not hard to write such an extension to the operations of std::list or any standard container. 此外,由于C ++具有自由函数,因此将这样的扩展写入std::list或任何标准容器的操作并不困难。

template<class Cont>
typename Cont::value_type return_pop_back(Cont& c){
  typename Cont::value_type v = c.back();
  c.pop_back();
  return v;
}

It should be noted, though, that the above function is not exception-safe, meaning if the return v; 但应该注意的是,上述函数不是异常安全的,这意味着如果return v; throws, you'll have a changed container and a lost object. 抛出,你将有一个更改的容器和丢失的对象。

In Java a pop of a general interface can return a reference to the object popped. 在Java中,通用接口的pop可以返回对弹出对象的引用。

In C++ returning the corresponding thing is to return by value. 在C ++中返回相应的东西是按值返回。

But in the case of non-movable non-POD objects the copy construction might throw an exception. 但是在不可移动的非POD对象的情况下,复制构造可能会引发异常。 Then, an element would have been removed and yet not have been made accessible to the client code. 然后,一个元素将被删除,但尚未被客户端代码访问。 A convenience return-by-value popper can always be defined in terms of more basic inspector and pure popper, but not vice versa. 一个方便的回传值波普总是可以在更基本的检查和纯波普尔来定义,而不是相反。

This is also a difference in philosophy. 这也是哲学上的差异。

With C++ the standard library only provides basic building blocks, not directly usable functionality (in general). 使用C ++,标准库仅提供基本构建块,而不是直接可用的功能(通常)。 The idea is that you're free to choose from thousands of third party libraries, but that freedom of choice comes at a great cost , in usability, portability, training, etc. In contrast, with Java you have mostly all you need (for typical Java programming) in the standard library, but you're not effectively free to choose (which is another kind of cost). 这个想法是你可以从成千上万的第三方库中自由选择,但是选择的自由需要很高的成本 ,可用性,可移植性,培训等。相比之下,使用Java你几乎拥有所有你需要的(对于标准库中的典型Java编程),但您无法自由选择(这是另一种成本)。

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

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