Is it bad to have vector in a public interface?

published at 12.07.2015 23:31 by Jens Weller
Save to Instapaper Pocket

After I finished my talk at NDC Oslo about encryption in C++, the last question I was asked by an attendee was about having std::vector in public interfaces as an argument, and if that would be considered bad practice. So, is it good or bad to use std::vector in a public interface?

Lets create a simple interface and see:

template<class T>
void test(std::vector<T> vec);//1
template<class T>
void test(std::vector<T>& vec);//2
template<class T>
void test(const std::vector<T>& vec);//3

So, there are 3 options worth looking at IMHO: taking a vector by value, reference and const reference. You also could have a pointer to a vector as an argument, but this would behave similar to a reference, except that you could pass a null pointer instead of a vector pointer. Also, forwarding references and rvalue references are special use cases I will ignore for this post. You might want to read up on those, Scott Meyers Effective Modern C++ has a very good chapter on this.

While I also will look at C++11, the person asking, is still living in a C++98 code base. So, first lets see how things used to be, before Modern C++ became a standard. Essentially, the question is about passing potential big objects into interfaces.

Lets look, how the 3 options behave at run time:

For more details on passing (and returning), look at the slides of Eric Nieblers Keynote "C++11 and No-Compromise Library Design" at Meeting C++ 2013. This talk was recorded at C++Now a year later:

So, is it good?

Its clear, that the best option should be passing by const reference or by reference if there is the need to make changes to the vector. At least that is the case, if the object passed into a function is potentially big. Which applies to vector, so, void print_options(const std::vector<str::string>& options); would be the correct way to pass a vector of strings to print_options. It is important, that you avoid copies in interfaces, when they are not needed. Taking a copy in a constructor and moving it into a member would be fine in C++11, while in C++98 a const reference would seem more natural for the same interface.

Yet, one thing makes me wonder ever since NDC Oslo, while we know how to pass objects like std::vector into interfaces correctly, the STL does not do so very often. The above mentioned std::getline is an exception, while in Qt often collections are passed to interfaces such as methods and functions. The STL prefers to not pass containers into functions, it prefers to pass iterators. The common STL interface for algorithms is begin and end iterator, often accompanied with some other parameters. And the STL does so in a generic way.

This also reveals that often the common nature on working with containers is to do something with their elements, and not the container. So, if that is the case, you should think about, if an iterator based interface is not the far better approach. Maybe also, that you don't need to write this function, because there is already an algorithm in the standard enabling you to this. print_options for example could be replaced with a call to std::copy using an ostream_iterator.

But the STLs interface leads to a lot of code like algorithmX(vec.begin(), vec.end(), ...);, so its not perfect. That is why libraries such as boost::range exist, to simplify this interface, especially when the whole container is meant to be passed in. But ranges go beyond this, and actually its Eric Niebler, who is now working on a standard version for ranges. At this years C++Now he gave a very good keynote about his range library, which is already available.

Yet, other libraries, such as wxWidgets or Qt often will pass containers and objects into interfaces. Qt often uses copy on write for their own types, hence passes most objects by value, as they are just handles to the reference counted data object hidden by the implementation. Qt is also known to have very well designed interfaces and APIs...

So, at the end, the correct answer seems to be: it depends, which design you prefer.

Here is my opinion:

 

Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!