Как работать с массивами (объявленными в стеке) в C ++?

StackOverflow https://stackoverflow.com/questions/55093

  •  09-06-2019
  •  | 
  •  

Вопрос

У меня есть класс для анализа матрицы, который сохраняет результат в элементе массива:

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, выходит за пределы области видимости, любой код, который пытается получить доступ к матрице через эту ссылку, теперь будет ссылаться на объект вне области видимости, и вы потерпите крах.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top