There really isn't as large of a cost as you seem to think. You write "suck it up and return a vector", but this is actually one of the fastest solutions: std::vector<foo> bar = GetFooVector(); The returned object results in creating a single collection and the returned result is used directly, no copies are required.
If you assign to an existing object there is a cost to get rid of what used to be in there, but again the cost is minimal: bar = GetFooVector(); The assignment operator needs to wipe out whatever used to be in the old object, and there is a cost to creating the additional objects, but it is typically a bearable cost, particularly with your vector if integer values.
If someone is using it with an existing collection: bar.swap( GetFooVector() ); could be similar cost if the function's return result is compatible.
If you are not completely replacing the collection with another collection there needs to be some copying that takes place from one buffer to another buffer. For that, there is the option to use the insert() function family or use std::copy() with a back inserter. C++ gives plenty of options.
Really I want to do number 1 but the global new/delete can be on the slow side.
Then do it and don't use global new/delete. The std::vector template takes more than one parameter, although it is optional. All the container classes have a second optional template value for the memory allocator to use. By default it is the global allocator but you are under no obligation to use it. If you want to use a different allocator then do so. Code like std::vector<foo, myallocator> works just fine.
2-4 are just work arounds that I really don't want to use if I don't have to.
Not really, they do something different.
#2 is a great solution if your intention is to reuse an object. Often when you want to reuse something you don't clear it, allowing the calling code to accumulate a large number of items. Maybe they want to accumulate all the items that satisfy multiple searches.
#3 is sometimes necessary if a standard collection type doesn't quite satisfy your needs. For example, in older C++ there was no equivalent of a hash map or hash table in the standard, and many programs either used a hash map library or wrote their own. There is no shame in that, just be aware when you are doing it that somebody out there in the world has probably already implemented the solution for you and you don't need to spend your time writing and debugging your own, effectively re-inventing the wheel.
#4 is perfectly acceptable to if that is really what you need to do. Sometimes you'll have a collection internally that you want to return but you don't want other people manipulating your class's internals; returning a const reference to your internals allows them to see the item and use the contents, but not actually manipulate your collection.
So to that end, how do you guys handle variable length arrays/returns? Do you just use new/delete and don't worry about performance (even in tight/critical loops)?
Normally with option 1 as you described. Create a container in the function and return it, trusting the class user will make smart choices either to use RVO or to use move operators with the result.
Also, be sure to reserve enough space in your function so there is only a single allocation cost. It is frustrating to profile code and discover that someone is doing hundreds of push_back() calls where each one causes the container to expand. The reserve() function is your friend, and you should use it before doing any batch work.