Why not implement contains function in c++ Containers?

安稳与你 提交于 2019-12-23 02:58:24

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!