The code below explores different implementations of range(n)
, a function
that returns the vector<int>
{0, 1, 2, ..., n - 1}
.
// range.cpp
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
template<class T>
ostream& operator<<(ostream& os, const vector<T>& v) {
int n = v.size();
if (n == 0) {
os << "{}";
} else if (n == 1) {
os << "{" << v[0] << "}";
} else {
os << "{" << v[0];
for(int i = 1; i < v.size(); ++i) {
os << ", " << v[i];
}
os << "}";
}
return os;
}
// By default, C++ returns a copy of the value that it returns in a function.
// So, for functions like range (below), this can be expensive. However, since
// C++11, there's a neat trick called "move semantics" that can, in some
// cases, le the compiler optimize-away the need to make a copy of the
// returned value. Code like range is now actually quite efficient, since it
// probably doesn't make a copy of the result vector (but instead returns a
// copy of the underlying pointer to the array in the vector).
vector<int> range(int n) {
if (n <= 0) n = 0;
vector<int> result(n);
for(int i = 0; i < n; ++i) {
result[i] = i;
}
return result;
}
// This version of range creates a vector on the free store using new, and
// then returns a pointer to it. The pointer is copied when returned, but the
// vector it points to is not copied. Thus it doesn't pointlessly copy the
// result. However, it introduces a potentially more serious and subtle bug:
// it is up to the code that calls range_raw_ptr to remember to call delete to
// avoid a memory leak. In practice, this is easier said than done. If you
// forget to call delete, you get a memory leak, i.e. the vector is never
// deleted and the memory it takes up cannot be re-used. If you call delete
// too soon, you may get pointer de-referencing errors. Also, you must ensure
// that you call delete exactly *once*; calling delete two or more times on
// the same pointer is usually a serious error that corrupts memory.
vector<int>* range_raw_ptr(int n) {
if (n <= 0) n = 0;
// new vector<int>(n) returns a pointer to a newly created vector<int> on
// the free store
vector<int>* result = new vector<int>(n);
for(int i = 0; i < n; ++i) {
(*result)[i] = i;
}
return result;
}
// range_unique_ptr follows the same idea as range_raw_ptr, but it uses a
// unique_ptr instead of a raw pointer. The value of this is that a unique_ptr
// will automatically (and correctly!) delete the memory it refers to so there
// are no memories leaks or other problems with pointers. In practice, this is
// often very useful, and a good reason to avoid raw pointers. C++ has other
// similar pointer classes for more complex situations, but for us unique_ptr
// should be enough.
//
// Please note that this is probably not the best way to actually write range.
// The first version that returns a plain vector<int> is probably the best
// in C++11 and later.
unique_ptr<vector<int>> range_unique_ptr(int n) {
if (n <= 0) n = 0;
unique_ptr<vector<int>> result(new vector<int>(n));
for(int i = 0; i < n; ++i) {
(*result)[i] = i;
}
return result;
}
void test_range() {
vector<int> a = range(10);
cout << a << "\n";
vector<int>* b = range_raw_ptr(10);
cout << *b << "\n";
delete b;
unique_ptr<vector<int>> c = range_unique_ptr(10);
cout << *c << "\n";
// no need to delete c --- unique_ptr automatically deletes the object it
// points to when it goes out of scope
}
int main() {
test_range();
}