Как работать с массивами (объявленными в стеке) в C ++?
Вопрос
У меня есть класс для анализа матрицы, который сохраняет результат в элементе массива:
class Parser
{
...
double matrix_[4][4];
};
Пользователю этого класса необходимо вызвать функцию API (например, функцию, над которой у меня нет контроля, поэтому я не могу просто изменить ее интерфейс, чтобы упростить работу), которая выглядит примерно так:
void api_func(const double matrix[4][4]);
Единственный способ, который я придумал для вызывающего объекта передать результат массива функции, - это сделать член общедоступным:
void myfunc()
{
Parser parser;
...
api_func(parser.matrix_);
}
Это единственный способ что-то сделать?Я поражен тем, насколько негибкими являются многомерные массивы, объявленные подобным образом.Я думал matrix_
по сути, это было бы то же самое, что и double**
и я мог бы колдовать (безопасно) между ними двумя.Как оказалось, я даже не могу найти небезопасный способ колдовать между вещами.Допустим, я добавляю средство доступа к Parser
класс:
void* Parser::getMatrix()
{
return (void*)matrix_;
}
Это будет скомпилировано, но я не могу его использовать, потому что, похоже, нет способа привести обратно к типу массива weirdo:
// A smorgasbord of syntax errors...
api_func((double[][])parser.getMatrix());
api_func((double[4][4])parser.getMatrix());
api_func((double**)parser.getMatrix()); // cast works but it's to the wrong type
Ошибка заключается в:
ошибка C2440:"приведение типа" :не удается преобразовать из 'void *' в 'const double [4][4]'
...с интригующим дополнением:
Преобразования в типы массивов отсутствуют, хотя есть преобразования в ссылки или указатели на массивы
Я также не могу определить, как привести к ссылке или указателю на массив, хотя это, вероятно, мне здесь не поможет.
Конечно, на данный момент этот вопрос носит чисто академический характер, поскольку void*
касты едва ли чище, чем у одного участника класса, оставленного на публике!
Решение
Вот хороший, чистый способ:
class Parser
{
public:
typedef double matrix[4][4];
// ...
const matrix& getMatrix() const
{
return matrix_;
}
// ...
private:
matrix matrix_;
};
Теперь вы работаете с описательным именем типа, а не с массивом, но поскольку это typedef
компилятор по-прежнему разрешает передавать его неизменяемой функции API, которая принимает базовый тип.
Другие советы
Попробуй это.Он чисто компилируется на gcc 4.1.3:
typedef double FourSquare[4][4];
class Parser
{
private:
double matrix_[4][4];
public:
Parser()
{
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
matrix_[i][j] = i*j;
}
public:
const FourSquare& GetMatrix()
{
return matrix_;
}
};
void api_func( const double matrix[4][4] )
{
}
int main( int argc, char** argv )
{
Parser parser;
api_func( parser.GetMatrix() );
return 0;
}
В прошлом я использовал подобное объединение для передачи матриц:
union matrix {
double dflat[16];
double dmatr[4][4];
};
Затем передайте указатель вашему установщику и скопируйте данные в матрицу вашего класса.
Есть способы справиться с этим иным образом (которые являются более общими), но, по моему опыту, это решение, как правило, самое чистое в конечном итоге.
Я думал, что matrix_ по сути будет таким же, как double**
В C существуют настоящие многомерные массивы, а не массивы указателей на массивы, поэтому double[4][4] - это непрерывный массив из четырех массивов double[4], эквивалентный double[16], а не a (double*)[4].
Преобразования в типы массивов отсутствуют, хотя есть преобразования в ссылки или указатели на массивы Приведение значения к double[4][4] привело бы к попытке создать значение в стеке - эквивалентно std::string(parser.getMatrix()) - за исключением того, что массив не предоставляет подходящего конструктора.Вы, вероятно, не хотели этого делать, даже если бы могли.
Поскольку тип кодирует шаг, вам нужен полный тип (double[][] не подойдет).Вы можете переосмыслить приведение void* к ((double[4][4])*),, а затем взять ссылку.Но проще всего ввести определение матрицы и вернуть ссылку правильного типа в первую очередь:
typedef double matrix_t[4][4];
class Parser
{
double matrix_[4][4];
public:
void* get_matrix () { return static_cast<void*>(matrix_); }
const matrix_t& get_matrix_ref () const { return matrix_; }
};
int main ()
{
Parser p;
matrix_t& data1 = *reinterpret_cast<matrix_t*>(p.get_matrix());
const matrix_t& data2 = p.get_matrix_ref();
}
Чтобы подробнее остановиться на выбранном ответе, обратите внимание на эту строку
const matrix& getMatrix() const
Это здорово, вам не нужно беспокоиться о указателях и приведении.Вы возвращаете ссылка к базовому объекту matrix.Ссылки IMHO - одна из лучших особенностей C ++, которой мне не хватает при написании кода на обычном C.
Если вы не знакомы с разницей между ссылками и указателями в C ++, читать это
Во всяком случае, вы должны знать, что если Parser
объект, которому фактически принадлежит базовый объект matrix, выходит за пределы области видимости, любой код, который пытается получить доступ к матрице через эту ссылку, теперь будет ссылаться на объект вне области видимости, и вы потерпите крах.