When looking over the member functions of the STL containers, an odd thought occurred to me. Why don\'t functions like std::vector
not ha
Interesting question. The obvious return value would be the vector (or whatever) that the operation takes place on, so you could then write code like:
if ( v.push_back(42).size() > n ) {
// do something
}
I personally don't like this style, but I can't think of a good reason not to support it.
I am not sure, but I think that one of the reasons why the mutating std::string
members return an iterator
is so that the programmer could obtain a non-const-iterator to the std::string
after a mutating operation without requiring a second "leak".
The std::basic_string
interface was designed to support a pattern called copy-on-write, which basically means that any mutating operation does not affect the original data, but a copy. For example, if you had the string "abcde"
and replaced 'a'
with 'z'
to get "zbcde"
, the data for the resulting string might occupy a different location in the heap than the data for the original string.
If you obtain a non-const-iterator of a std::string
, then a COW string implementation must make a copy (also called "leak the original"). Otherwise, the program can change the underlying data (and violate the read-only invariant) with:
char& c0 = *str.begin();
c0 = 'z';
But, after a string mutation operation, the resulting string object already has sole ownership of the data, so the string implementation does not need to leak its data a second time to make a non-const-iterator.
A std::vector
is different because it does not support copy-on-write semantics.
Note: I got the term leak from the libstdc++ implementation of std::basic_string
. Also, "leaking the data" does not mean that the implementation leaks memory.
EDIT: Here is the libstdc++ definition of std::basic_string<CharT, Traits, Alloc>::begin()
for reference:
iterator
begin()
{
_M_leak();
return iterator(_M_data());
}
Returning the added element, or the container in container member functions is not possible in a safe way. STL containers mostly provide the "strong guarantee". Returning the manipulated element or the container would make it impossible to provide the strong guarantee (it would only provide the "basic guarantee"). The reason behind this is, that returning something could possibly invoke an copy-constructor, which may throw an exception. But the function already exited, so it fulfilled its main task successfully, but still threw an exception, which is a violation of the strong guarantee. You maybe think: "Well then lets return by reference!", while this sounds like a good solution, its not perfectly safe either. Consider following example:
MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass&
Still, if the copy-assignment operator throws, we dont know if push_back succeded or not, thus indirectly violating the strong-guarantee. Even though this is not a direct violation. Of course using MyClass& bar = //...
instead would fix this issue, but it would be quite inconvenient, that a container might get into an indeterminate state, just because someone forgot a &
.
A quite similar reasoning is behind the fact that std::stack::pop()
does not return the popped value. Instead top()
returns the topmost value in a safe way. after calling top, even when a copy-constructor, or a copy-assignment constructor throws, you still know that the stack is unchanged.
EDIT: I believe returning an iterator for the newly added element should be perfectly safe, if the copy-constructor of the iterator-type provides the no-throw guarantee (and every i know of does).
Not sure they had a very good reason, but this function is slow enough already.
Maybe because it was not "needed"?
erase()
and insert()
have no other way than return an iterator to allow continuing a loop it was called in.
I don't see a good reason to support the same logic with push_back()
.
But sure, it would be be wonderful to make more cryptic expressions. (I don't see an improvement in your example, it looks like a good way to slow your coworkers when reading your code...)
I think it has to do with the concept of a return value: the return value is there not for your convenience but for a conceptual result of the 'computation' they apparently thought push_back conceptually doesn't result in anything.