问题
My initial question was: why does in C++ the contains()
function miss in Containers
?
So I looked for an explanation and I found something interesting about why some other function are not implemented in all the Containers
(essentially because of performance issues and convenience).
I know that you can use the find
function from algorithm
library or you can just write your own function with Iterator
, but what I can't understand is why in set
, for example, the contains
function(where it's called find
) is implemented, whereas in vector
or queue
it is not.
It's pretty clear to me too why Container classes does not share a common interface like Collections
do in Java (thanks to this answer) but in this case I can't find the reason why not implement the contains()
function in all the Containers classes (or at least in some like vector
).
Thank you
回答1:
There is a good reason that containers do not share a "common (inherited) interface" (like in Java
) and it is what makes the C++
generics so powerful. Why write code for every container when you can write it only once for all containers? This is one of the main principles the STL
was built on.
If using containers relied on member functions from a common inherited interface you would have to write a find function for every container. That is wasteful and poor engineering. Good design says if you can write code in only one place you should because you only have to remove the bugs from that one place, and fixing a bug in one place fixes the bug everywhere.
So the philosophy behind the STL
is to separate the algorithms from the containers so that you only have to write the algorithm once and it works for all containers. Once the algorithm is debugged, it is debugged for all containers.
A fly in that ointment is that some containers can make more efficient decisions due to their internal structure. For those containers a type specific function has been added which will take advantage of that efficiency.
But most functions should be separate from the containers. It is called decoupling and it reduces bugs while promoting code reuse, often much more so than polymorphism which is what libraries like Java
containers use (a common inherited interface).
回答2:
The reason why std::set
implements its own find
is that the "generic" std::find
does a linear search, while std::set
can do a lot better than that by searching its tree representation in O(log2 n ).
std::vector
and std::list
, on the other hand, cannot be searched faster than in linear time, so they rely on std::find
implementation.
Note that you are still allowed to apply std::find
to std::set
to search it linearly; it just wouldn't be as efficient as using set's own find
member function.
std::set<int> s {1, 2, 3, 4, 5, 6};
auto res = std::find(s.begin(), s.end(), 3);
std::cout << *res << std::endl; // prints 3
回答3:
Because it's a bad design pattern. If you're using "find" on a linear container repeatedly, there's a problem with your code. The average and worst case time complexity is still O(n), which means you've made a bad choice.
For example, std::map
and std::unordered_map
have find
member functions which allows O(logn) and O(1) lookups. This is because the container employs efficient item lookup via these methods: that is how the container should be used.
If you've weighed all the options, and decided a linear container is the best model for your data, but do need to find an element in rare occasions, std::find()
allows you to do just that. You shouldn't rely on it. I view this as an antipattern in Python and Java, and a benefit in C++.
Just as a personal note, 3 years ago, when I was a beginner coder, I wrote if mydict in list: do_something()
a lot. I thought this was a good idea, because Python makes list item membership idiomatic. I didn't know better. This led me to produce awful code, until I learned why linear searches are so inefficient compared to binary searches and hashmap lookups. A programming language or framework should enable good design patterns, and discourage bad ones. Enabling linear searches is a bad design pattern.
回答4:
Other answers for some reason focus on complexity of generalized and per-container find
methods. However they fail to explain the OP's question. The actual reason for a lack of helpful utility methods originates from the way the classes from different files are used in C++. If every container that does not hold any special properties would have contains
method executing generic search with linear complexity we would stuck either with situation when each container header also includes <algorithm>
or when each container header reimplements it's own find
algorithm (which is even worse). And the pretty large document build during compilation of each translation unit after preprocessor includes (that is copy-pastes) every included header would grow even bigger. Compilation would take even more time. And the rather cryptic compilation error messaged may get even longer. That old principle of not making member function when a non-member function can do the job (and to include only stuff you are going to use) exists for a reason.
Note that there was a proposal recently for uniform call syntax that may allow to kinda mix-in utility methods into classes. If it goes live it probably will be possible to write extension functions like this:
template< typename TContainer, typename TItem > bool
contains(TContainer const & container, TItem const & item)
{
// Some implementation possibly calling container member find
// or ::std::find if container has none...
}
::std::vector< int > registry;
registry.contains(3); // false
来源:https://stackoverflow.com/questions/44864338/why-not-implement-contains-function-in-c-containers