Как бороться с массивами (объявленными в стеке) в C ++? - PullRequest
15 голосов
/ 10 сентября 2008

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

class Parser
{
  ...
  double matrix_[4][4];
};

Пользователь этого класса должен вызвать функцию API (например, функцию, которую я не могу контролировать, поэтому я не могу просто изменить ее интерфейс, чтобы сделать работу более простой), которая выглядит следующим образом:

void api_func(const double matrix[4][4]);

Единственный способ, которым я придумала, чтобы вызывающая сторона передавала результат массива функции, это сделав элемент public:

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* броски едва ли чище, чем один ученик оставил публику!

Ответы [ 5 ]

16 голосов
/ 10 сентября 2008

Вот хороший, чистый способ:

class Parser
{
public:
   typedef double matrix[4][4];

   // ...

   const matrix& getMatrix() const
   {
      return matrix_;
   }

   // ...

private:
  matrix matrix_;
};

Теперь вы работаете с описательным именем типа, а не с массивом, но, поскольку это typedef, компилятор все равно разрешит передавать его неизменяемой функции API, которая принимает базовый тип.

6 голосов
/ 10 сентября 2008

Попробуй это. Чисто компилируется на 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;
}
4 голосов
/ 11 сентября 2008

Я думал, что matrix_ по сути будет такой же, как двойной **

В C есть настоящие многомерные массивы, а не массивы указателей на массивы, поэтому double [4] [4] - это непрерывный массив из четырех double [4] массивов, эквивалентных double [16] а (двойной *) [4].

Нет преобразований в типы массивов, хотя есть преобразования в ссылки или указатели на массивы. Преобразование значения в double [4] [4] будет пытаться создать его в стеке - эквивалентно std :: string (parser.getMatrix ()) - за исключением того, что массив не предоставляет подходящего конструктора. Вы, вероятно, не хотели этого делать, даже если бы могли.

Поскольку тип кодирует шаг, вам нужен полный тип (double [] [] не подойдет). Вы можете переинтерпретировать приведение void * к ((double [4] [4]) *), а затем взять ссылку. Но проще всего ввести typedef матрицы и вернуть ссылку правильного типа:

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();
}
4 голосов
/ 10 сентября 2008

В прошлом я использовал подобное объединение для обхода матриц:

union matrix {
    double dflat[16];
    double dmatr[4][4];
};

Затем передайте указатель вашему сеттеру и скопируйте данные в матрицу вашего класса.

Есть способы справиться с этим иначе (которые являются более общими), но это решение, как правило, является самым чистым в конце, по моему опыту.

2 голосов
/ 11 сентября 2008

Чтобы уточнить выбранный ответ, соблюдайте эту строку

const matrix& getMatrix() const

Это здорово, вам не нужно беспокоиться об указателях и кастинге. Вы возвращаете ссылку на базовый матричный объект. ИМХО ссылки - одна из лучших функций C ++, которую мне не хватает при кодировании на прямом C.

Если вы не знакомы с разницей между ссылками и указателями в C ++, прочитайте это

В любом случае, вы должны знать, что если объект Parser, который фактически владеет базовым объектом матрицы, выходит из области видимости, любой код, который пытается получить доступ к матрице через эту ссылку, теперь будет ссылаться на out-out. объекта, и вы потерпите крах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...