A great deal here (almost everything, to be honest) depends on what you're really planning to do.
A few things are pretty easy though. First, you should almost never use malloc
or free
in C++. While there may be a few situations in which it's (arguably) reasonable to do so, they're unusual, and probably not something you should even consider until you really know what you're doing (at which time, you'll probably reject them, regardless of my advice). Likewise, you should avoid using raw pointers. Again, you might eventually encounter a situation where they're the best tool available, but for now (and the next year or two, at least) it's probably best to nearly forget that they exist at all.
Smart pointers are kind of a half-way point. While certainly a lot more acceptable than a raw pointer, they're still (at least in my opinion) less than desirable. Personally, I almost never use them either, though there are some pretty good C++ coders who use them a lot more than I do.
Some have already advised that you should typically work with something like an std::vector
instead of using a pointer at all -- and I'd agree that this is good advice. I'd advise that you at least consider going a step further still: to the extent that it's at all reasonable, you should write your code as generic algorithms that work with iterators instead of working directly with a container.
For a simple example, let's consider an array of numbers, of which we want to find the arithmetic mean. Using raw memory, this might look something like:
double mean(double *data, size_t size) {
double total = 0.0;
for (int i=0; i<size; i++)
total += data[i];
return total / size;
}
As pointer usage goes, that's fairly innocuous, but they're right that it can be cleaner if we use an std::vector
:
double mean(std::vector const &v) {
double total = 0.0;
for (int i=0; i<v.size(); i++)
total += v[i];
return total / v.size();
}
Using iterators with an algorithm for the standard library, we can simplify that a bit though:
template <class Iter>
double mean(Iter b, Iter e) {
return std::accumulate(b, e, 0.0) / std::distance(b, e);
}
With this, we're no longer tied to one particular container type. For example, if we happen to have the numbers stored in a std::deque
or std::list
instead of a std::vector
, this will still work just fine:
std::deque<double> numbers { 1.2, 3.4, 5.6, 7.8};
double average = mean(numbers.begin(), numbers.end());
std::list<double> more_numbers { 1.414, 1.732, 2.0 };
double another_average = mean(more_numbers.begin(), more_numbers.end());
This can also work for situations where we're producing multiple results, for which we need to allocate space. For example, if we took one collection of data as input, and produced another collection of data as output, we might call something like:
std::vector<something> results;
std::my_algorithm(input.begin(), input.end(), std::back_inserter(results));
In this case, the back_inserter
returns an instance of a std::back_insert_iterator<T>
, which will use the vector's push_back
to insert results when we write to the iterator.