Why the error?
C++ has much tighter rules than C (and calloc
is a C function) about what kind of conversions the compiler will perform for you when performing an assignment. Assigning void *
to anything but void *
requires you to tell the compiler, "Yes, this looks stupid and is risky, but I know what I'm doing."
Why is this an error?
There is a long history of botched implicit conversions through the annals of C and C++ programming. Making this conversion an error at least makes the programmer stop and think about it before continuing.
What can I do about it?
In this case the most logical simple solution is
std::vector<std::vector<double>> descriptive_name_goes_here(n+1, std::vector<double>(n+1));
This will allocate all of the required storage and set it all to 0. It manages its own memory including deleting and resizing as required. It's pretty much fire and forget.
Documentation on std::vector
After this comes a variety of smart pointers, particularly
std::unique_ptr<std::unique_ptr<double[]>[]> descriptive_name_goes_here =
std::make_unique<std::unique_ptr<double[]>[]>(n+1);
and a for
loop to make_unique
the inner dimensions and assign all of the allocated values to 0.
A unique_ptr
handles memory management, making sure the allocated data it refers to is destroyed when it is no longer referenced. As a member variable, unique_ptr
can make passing around or storing an object in a container a pain in the neck because if a unique_ptr
could be copied, it wouldn't necessarily be all that unique, would it? vector
handles that nonsense for you.
Documentation on std::unique_ptr
Documentation on std::make_unique
If somehow the above two are impossible, and by 2017 this should be a wholly artificial restriction or the result of operating on legacy code requiring a legacy compiler, use
double ** descriptive_name_goes_here = new double*[n+1]
and a for
loop to new
the inner dimensions and assign all of the allocated values to 0.
This option places all of the memory management responsibility on the programmer, and even the gods of programming occasionally miss a case where a delete[]
was required. It also means you the programmer are responsible to supporting the Rules of Three and Five to make certain the destruction, copy, assignment, and move logic in any wrapping classes is correct.
This is the absolute last resort for low-level work like writing a vector
or smart pointer.
The only place for calloc
in c++ would be in bizarre edge cases and frankly I can't think of any right now. It's very possible there aren't any.
With the options out of the way...
May I recommend making a simple matrix class out of a 1D array? The array of arrays approach looks nice and simple and elegant, and often has utterly terrible cache performance. Each of these arrays of arrays (or vectors of vectors) is comprised of many different blocks of memory scattered around in storage. This makes it more difficult for the CPU to predict where to go next and what data to load. Every time the CPU has to stop and wait for the cache to be loaded you suffer performance degradation measured in orders of magnitude.
#include <iostream>
#include <vector>
class Matrix
{
private:
size_t rows, columns;
std::vector<double> matrix;
public:
Matrix(size_t numrows, size_t numcols) :
rows(numrows), columns(numcols), matrix(rows * columns)
{
}
double & operator()(size_t row, size_t column)
{
// check bounds here
return matrix[row * columns + column]; // note 2D coordinates are flattened to 1D
}
double operator()(size_t row, size_t column) const
{
// check bounds here
return matrix[row * columns + column];
}
size_t getRows() const
{
return rows;
}
size_t getColumns() const
{
return columns;
}
};
This is easily turned into a template for generalizing the data stored