12

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. For example, list doesn't have simple accessors like get(index) that I'm used to in Java. Methods like pop_back and pop_front don't return the object in the list either. So you have to do something like:

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

Instead of something simple like: 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. Why is the STL containers for C++ designed like this? Are common operations like this that I do in Java not so common for C++?

edit: Sorry, I guess my question was worded very poorly to get all these downvotes, but surely somebody could have edited it. To clarify, I'm wondering why the STL data structures are created like this in comparison to Java. 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.

telkins
  • 10,440
  • 8
  • 52
  • 79
  • 5
    We have iterators and operator overloading, as well as the exception safety that comes from not returning anything. – chris Oct 29 '12 at 19:56
  • 2
    When writing C++, forget everything you're used to from Java. Also, it's not like it's hard to write a function `return_pop_back` that does what you want. – Xeo Oct 29 '12 at 19:57
  • There is exactly one behavior I miss about java collections and it's a combined access and pop. All of the other features c++ provides in a way that I perceive to be subjectively better. – Wug Oct 29 '12 at 19:58
  • 2
    Why so many downvotes? It's a well thought out question, if highly biased. – Mooing Duck Oct 29 '12 at 19:58
  • as @chris said C++ has built in safety nets as well as C++ allows you to get the highest frame rates in gaming programming; however What is your question here? – Robert Oct 29 '12 at 19:59
  • 3
    `std::list::get(index)` doesn't exist because inefficient methods are generally not exposed from the STL. The STL list is a doubly linked list: about the only reason to use one is because you need to be able to splice with O(1) cost -- if you want a method to ask for the nth element of the list, you are almost certainly screwing up and using the wrong container. There are ways to get the nth element of a `std::list` (get `begin()`, then `advance()` it n steps), but they are more expensive than they are hard to write. – Yakk - Adam Nevraumont Oct 29 '12 at 20:11
  • I reworded the question. Any chance on getting this opened again? Not sure why this stirred the hornet's nest up so much. Just doesn't make sense why the STL containers operate this way when if there were that much of a performance decrease then I doubt it would be standard in Java. – telkins Oct 29 '12 at 20:27
  • 2
    @Yakk: `std::list::splice` is guaranteed `O(N)` in the number of spliced elements in C++11 since `std::list::size` is guaranteed `O(1)`. :( – Xeo Oct 29 '12 at 20:43
  • @Xeo bah, humbug. So the corner case for `std::list` use got smaller. (I sort of get it, having `size()` take forever is also bad) – Yakk - Adam Nevraumont Oct 29 '12 at 21:26

7 Answers7

11

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.

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;. In C++, however, X x;, by itself, defines an object of type X, not just a reference to an object. We can use that object directly, not via a reference (i.e., 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. 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. 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. If the programmer wants to store pointers, s/he can certainly do so -- but the language doesn't require it like Java does. 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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
5

back() returns a reference to the final element of the vector, which makes it nearly free to call. pop_back() calls the destructor of the final element of the vector.

So clearly pop_back() cannot return a reference to an element that is destroyed. So for your syntax to work, pop_back() would have to return a copy of the element before it is destroyed.

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. 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.

There could be a pop-and-get-back method, but it would duplicate other functionality. 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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks, I think this is more or less what I was expecting to hear. Since the STL containers are meant to be more barebone, is there some other sort of list with more rich methods for me to use or am I going to have to implement them every time I want to use it in a project? Going back to my Java comparison, is it really that much more inefficient to return a copy of the object? Every method that performs an access/modification returns an object so I'm thinking it can't be that huge. – telkins Oct 29 '12 at 20:20
  • @trevor-e: Instead of trying to write Java code using C++, you would do much better learning to write C++ code in C++. – John Dibling Oct 29 '12 at 20:21
  • 1
    @trevor-e have a look at this [c++ containers reference](http://en.cppreference.com/w/cpp/container) wiki. There are many different types, they are actually not too hard to use, but you need to know their general characteristics. – juanchopanza Oct 29 '12 at 20:25
  • @JohnDibling I am trying to, but everyone in this tag thinks it is more appropriate to downvote and leave rather than help me learn the proper way to do it. Or does every single C++ programmer have their own custom library of different STL containers that they write in their free time? – telkins Oct 29 '12 at 20:31
  • 1
    @trevor-e: Not everyone in this tag feels that way. You might notice there are two votes to re-open this question. One of them is mine because, biased as your question might have been framed and asked, there is a nugget of an answerable question there. But I hope you can see how your question might be paraphrased similarly to "Why does C++ suck compared to Java?" Given that interpretation, I'm not suprised your question was downvoted and closed. – John Dibling Oct 29 '12 at 20:42
  • @trevor-e: Check again, most of the container functions return _references_ to the data, not copies. References are fast. Copies might be slow. Also, in Java, the main container might be an `ArrayList`, but in C++, using a `list` is rare and not recommended. 95% of the time you want to use `vector`, and 5% should be `deque`. – Mooing Duck Oct 29 '12 at 20:47
  • @JohnDibling Sorry if I came off that way then. I tried to edit the main post as best I could. I actually really like both! – telkins Oct 29 '12 at 20:49
  • 1
    @MooingDuck OK cool. So since everything is a reference, if I popped something off and the destructor is then called on that object, I obviously can't use that reference anymore right? That makes more sense. I thought that these structures more or less mapped over from Java, but it looks like I am very wrong haha. Will get to reading on these some more. Interesting usage statistics too. – telkins Oct 29 '12 at 20:52
  • So, the general rule of C++ std containers is "if you don't have a good reason, us a `std::vector`". Now, the `std::set` and `std::map` and their unordered cousins are often good reasons (but less often than you think), but std::list is a seriously special purpose container. `std::deque` is not half-bad, and you could use it as a "default" container without losing much. – Yakk - Adam Nevraumont Oct 29 '12 at 20:55
  • 1
    @Yakk: I wouldn't recommend `std::deque` as a default, because MSVC/dinkum implemented it in a _stupid_ way. But if they hadn't messed that up, I would have agreed with you. – Mooing Duck Oct 29 '12 at 20:58
  • @Mooing How bad is dinkum's deque? – Yakk - Adam Nevraumont Oct 29 '12 at 21:23
  • 1
    @Yakk: If it contains objects of 16 bytes or bigger, it acts similar to `vector>` but with a amortized O(1) `push_front` – Mooing Duck Oct 29 '12 at 21:30
  • @Mooing Wow: http://stackoverflow.com/questions/4097729/stddeque-memory-usage-visual-c-and-comparison-to-others 16 bytes per block? Gah. – Yakk - Adam Nevraumont Oct 29 '12 at 22:35
4

A list doesn't have a get(index) method because accessing a linked list by index is very inefficient. The STL has a philosophy of only providing methods that can be implemented somewhat efficiently. 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). 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.

Dirk Holsopple
  • 8,731
  • 1
  • 24
  • 37
  • OK, I'm starting to understand this now. The complexity philosophy seems weird to me. Why not leave it in the hands of the developer to decide if it is too costly to use or not? – telkins Oct 29 '12 at 20:37
  • 3
    @trevor-e: If `pop` were to return the object by value, it would be impossible for the developer to make that choice. The decision would already have been made, by the language. – John Dibling Oct 29 '12 at 20:43
  • 3
    @trevor-e: People who need to get values in a linked list by index are doing it wrong, and thus are not represented in experienced communities like the C++ standards committee. And so the function was not added to `std::list`, because nobody on the committee wanted it. There are a million other functions that *could* be part of the interface, there's no sense in adding ones that are of no use. – Benjamin Lindley Oct 29 '12 at 20:44
  • 1
    Plus C++ has free functions and function templates, which means that many of the functions in `` can be used. It's not like Java, where you're restricted to just the methods of the class itself. – MSalters Oct 30 '12 at 08:14
3

Why is the STL containers for C++ designed like this?

I think Bjarne Stroustrup put it best:

C++ is lean and mean. 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. The referent no longer exists because it was just pop()ed. It could be returned by pointer, but only if you make a new copy of the original, and that's wasteful. 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. General-purpose doesn't necessarily mean "easy to use for simple use cases" but "an appropriate platform for the widest range of applications."

John Dibling
  • 99,718
  • 31
  • 186
  • 324
2

Concerning pop()-like functions, there are two things (at least) to consider:

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.

2) These functions would return by value. 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. 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.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • How does value semantics relate to using `pop_back` when there is nothing to return? – Mooing Duck Oct 29 '12 at 19:59
  • `pop_xxx()` on an empty container is simply UB. :) – Xeo Oct 29 '12 at 19:59
  • I didn't downvote this, but although I like it, it isn't really an answer. That's probably why whoever did it did so, despite being antisocial about it. – Wug Oct 29 '12 at 20:00
  • @MooingDuck Something has to be returned by value. There is no concept of a null or empty representation for a type that can be returned by value. – juanchopanza Oct 29 '12 at 20:02
  • @juanchopanza: c++ does support exceptions. But I believe part of the magic of c++'s containers is that they don't throw them in every possible case like java's do. – Wug Oct 29 '12 at 20:03
  • @juanchopanza: (1) that's why we have functions that don't return values. (2) `pop_back` is UB when there is nothing to return. This is unrelated to value semantics. – Mooing Duck Oct 29 '12 at 20:04
  • 1
    @wug of course C++ supports exceptions, but at some point it was decided that a void-returning `pop()` is safer than value-returning and potentially throwing one. – juanchopanza Oct 29 '12 at 20:05
  • @MooingDuck (1) exactly, which is a likely reason `pop()` and co. return void. I get your point about value semantics. – juanchopanza Oct 29 '12 at 20:07
  • @MooingDuck: Can you provide a reference to something that says any container's behavior when popping from an empty container is UB? I'm looking but can't find anything. – Wug Oct 29 '12 at 20:11
  • @Wug: C++11 Jan2012, §21.4.6.5\11 "`void pop_back();` Requires: `!empty()`" – Mooing Duck Oct 29 '12 at 20:17
  • @juanchopanza: thanks, I was getting frustrated at our miscommunication. Now that it's fixed, changed -1 to +1. – Mooing Duck Oct 29 '12 at 20:19
  • Does that mean it will damage state if called while empty, or that it will fail in an obvious and detectable way – Wug Oct 29 '12 at 20:19
  • @Wug C++11 Jan2012, §1.3.24 "Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data." §17.3.21 "If such a function defined in a C++ program fails to meet the required behavior when it executes, the behavior is undefined." – Mooing Duck Oct 29 '12 at 20:20
  • Alright then. Now that I'm damn sure. Why exactly do no plebian references cite this as undefined behavior? – Wug Oct 29 '12 at 20:21
  • well crap. I'd better get out the emailatron – Wug Oct 29 '12 at 20:24
  • +1 for mentioning exception safety. it is indeed correctness considerations that cause the lack of pop-a-value methods. – Cheers and hth. - Alf Oct 29 '12 at 20:54
2

list doesn't even have simple accessors like 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. 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.

Methods like pop_back and pop_front don't return the object in the list either.

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.

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; throws, you'll have a changed container and a lost object.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • OK thanks, that makes more sense with the code that you provided, did not know it was possible to do that. For the complexity issue, wouldn't it make more sense to have the developer decide if the complexity is too much or not? – telkins Oct 29 '12 at 20:35
  • @trevor-e: It's not about letting somebody not decide, but about not hiding it from them. Any random-access method (which your `get(index)` would be) suggests `O(1)` random-access, by definition. – Xeo Oct 29 '12 at 20:39
  • @trevor-e: All of the `list` members are fast. If you want to do something slow, you have to do those 3-4 lines yourself. They chose this, because (1) it's not as irritating as it sounds, and (2) it makes certain the programmer is doing slow things _on purpose_ instead of accidentally. – Mooing Duck Oct 29 '12 at 20:53
1

In Java a pop of a general interface can return a reference to the object popped.

In C++ returning the corresponding thing is to return by value.

But in the case of non-movable non-POD objects the copy construction might throw an exception. 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). 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).

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331