Рекомендации по структурированию класса для указателя на одну из многих структур, определенных во время выполнения - PullRequest
0 голосов
/ 09 октября 2019

Я пишу парсер растровых изображений (bpm), чтобы практиковать c ++ во время обучения. Однако в разных версиях есть много разных заголовков изображений, и я наткнулся на стену о том, как подойти к этому, так как кажется, что он избегает многих проверок и выглядит элегантно. Не могли бы вы дать мне совет относительно некоторых из следующих подходов или предложить другие?

Вот подходы, о которых я подумал. Основная причина, по которой у меня возникли проблемы, заключается в том, что я хочу использовать memcpy, чтобы избежать копирования каждого поля по отдельности. Единственное, что согласуется во всех структурах - это первые 4 байта, определяющие размер заголовка / структуры.

  1. Используйте несколько указателей в классе. Установите каждый на nullptr при инициализации. Затем используйте один reinterpret_cast, чтобы определить, какой заголовок использовать при первом анализе файла. С этого момента проверьте, какой указатель не является nullptr, чтобы определить, какой из них использовать.

  2. Используйте poid void и используйте reinterpret_cast всякий раз, когда выполняете операцию получения или установки значений взаголовок. Вероятно, не лучший вариант, но он есть.

  3. Создайте ABC (что, я не уверен, я полностью понимаю). Позволил бы для структуры наследования. Не уверен, что это будет работать гладко с memcpy, потому что из того, что я понимаю, наличие виртуальных методов добавит невидимый указатель в начале структуры, который сломается при использовании неизмененного буфера, скопировав его непосредственно в структуру. (Пожалуйста, скажите мне, если я ошибаюсь по этому поводу.)

В настоящее время пытаюсь приблизиться к одному. Вот мой заголовочный файл.

#ifndef BITMAP_H_INCLUDE
#define BITMAP_H_INCLUDE

#include <fstream>
#include <iostream>
#include <vector>
#include <cstring>

enum HeaderSizes
{
  File = 14,
  Core = 12,
  InfoV1 = 40,
  OS2 = 64,
  InfoV4 = 108,
  InfoV5 = 124,
};

enum InfoCompressionMethod
{
  // omitted for brevity
};

enum HalftoneAlgorithms
{
  // omitted for brevity
};

struct FileHeader
{
  // omitted for brevity, first header is always 14 bytes
};

struct CoreHeader
{
  uint32_t header_size;
  uint16_t width;
  uint16_t height;
  uint16_t planes;
  uint16_t bit_depth;
};

struct InfoV1Header
{
  uint32_t header_size;
  int32_t width;
  int32_t height;
  uint16_t planes;
  uint16_t bit_depth;
  uint32_t compression;
  uint32_t size_image;
  int32_t x_resolution;
  int32_t y_resolution;
  uint32_t color_used;
  uint32_t color_important;
};

struct OS2Header : public InfoV1Header
{
  uint16_t resolution_units;
  uint16_t reserved;
  uint16_t fill_direction;
  uint16_t halftone_algorithm;
  uint32_t halftone_param_1;
  uint32_t halftone_param_2;
  uint32_t color_encoding;
  uint32_t application_defined;
};

struct InfoV4Header : public InfoV1Header
{
  uint32_t red_mask;
  uint32_t green_mask;
  uint32_t blue_mask;
  uint32_t alpha_mask;
  uint32_t cs_type;
  uint64_t red_x;
  uint64_t red_y;
  uint64_t red_z;
  uint64_t green_x;
  uint64_t green_y;
  uint64_t green_z;
  uint64_t blue_x;
  uint64_t blue_y;
  uint64_t blue_z;
  uint32_t gamma_red;
  uint32_t gamma_green;
  uint32_t gamma_blue;
};

struct InfoV5Header : public InfoV4Header
{
  uint32_t  intent;
  uint32_t  profile_data;
  uint32_t  profile_size;
  uint32_t  reserved;
};

class Bitmap
{
  public:

    Bitmap(std::string in_path);
    ~Bitmap();
    void save(std::string out_path);

  private:

    FileHeader* file_header;

    // Info Header Options
    CoreHeader* core_header;
    OS2Header* os2_header;
    InfoV1Header* info_v1_header;
    InfoV4Header* info_v4_header;
    InfoV5Header* info_v5_header;

    void verify_file_header() const;

};

#endif /* BITMAP_H_INCLUDE */

Кажется, что сейчас это лучший вариант, но я не согласен с идеей написать кучу методов, которые начинаются с проверки на nullptr в заголовках информации. Надеюсь, мне чего-то не хватает, потому что я не очень хорошо знаю c ++.

(В настоящее время работаю в c ++ 14, но не против использования 17, если это имеет смысл. В Linux Zorin OS 15, используя gcc 7.4,с помощью cmake 3.15.2.)

1 Ответ

0 голосов
/ 09 октября 2019

Случайная несвязанная точка, поля red_x - blue_z в заголовке V4 - это 32-битные целые числа со знаком, а не 64-битные уинты, как у вас выше (на самом деле это тип FXPT2DOT30 - фиксированная точка с 2-битной целочисленной частью, 30-битным десятичным знаком).

Типичный способ решения этой проблемы - загрузить весь файл BMP в один большой кусок памяти, а затем просто заново интерпретировать ваш путь к данным. Как вы заметили, V5 происходит от V4, который происходит от V1. Учитывая, что размер заголовка определяет, какой тип заголовка находится в файле, вы можете выполнить динамическое приведение плохого человека ....



struct InfoV1Header;
struct OS2Header;
struct InfoV4Header;
struct InfoV5Header;

struct InfoV1Header
{
  uint32_t header_size;
  int32_t width;
  int32_t height;
  uint16_t planes;
  uint16_t bit_depth;
  uint32_t compression;
  uint32_t size_image;
  int32_t x_resolution;
  int32_t y_resolution;
  uint32_t color_used;
  uint32_t color_important;

  template<typename T>
  inline const T* as() const;
};

struct OS2Header : public InfoV1Header
{
  uint16_t resolution_units;
  uint16_t reserved;
  uint16_t fill_direction;
  uint16_t halftone_algorithm;
  uint32_t halftone_param_1;
  uint32_t halftone_param_2;
  uint32_t color_encoding;
  uint32_t application_defined;
};

struct InfoV4Header : public InfoV1Header
{
  uint32_t red_mask;
  uint32_t green_mask;
  uint32_t blue_mask;
  uint32_t alpha_mask;
  uint32_t cs_type;
  int32_t red_x;
  int32_t red_y;
  int32_t red_z;
  int32_t green_x;
  int32_t green_y;
  int32_t green_z;
  int32_t blue_x;
  int32_t blue_y;
  int32_t blue_z;
  uint32_t gamma_red;
  uint32_t gamma_green;
  uint32_t gamma_blue;
};

struct InfoV5Header : public InfoV4Header
{
  uint32_t  intent;
  uint32_t  profile_data;
  uint32_t  profile_size;
  uint32_t  reserved;
};

template<>
inline const OS2Header* InfoV1Header::as<OS2Header>() const
{
  return header_size == sizeof(OS2Header) ? (const OS2Header*)this : 0;
}

template<>
inline const InfoV4Header* InfoV1Header::as<InfoV4Header>() const
{
  return header_size >= sizeof(InfoV4Header) ? (const InfoV4Header*)this : 0;
}

template<>
inline const InfoV5Header* InfoV1Header::as<InfoV5Header>() const
{
  return header_size >= sizeof(InfoV5Header) ? (const InfoV5Header*)this : 0;
}

Это позволит вам работать с данными. чуть более управляемым способом, чем поддержание многочисленных указателей на различные структуры (которые будут иметь один и тот же адрес - так что не стоит хранить их несколько раз imho) .

void doStuff(const InfoV1Header* v1)
{
  // do stuff with the v1 fields

  if(const OS2Header* os2 = v1->as<OS2Header>())
  {
    // do stuff with os2 fields
  }

  if(const InfoV4Header* v4 = v1->as<InfoV4Header>())
  {
    // do stuff with v4 fields
  }

  if(const InfoV5Header* v5 = v1->as<InfoV5Header>())
  {
    // do stuff with v5 fields
  }
}
...