Постоянная корректность указателей на безопасные проверки при инициализации - PullRequest
0 голосов
/ 04 июля 2018

Рассмотрим класс, который выделяет блок памяти для использования:

typedef unsigned char byte;

class ByteBuffer
{
   byte* const m_begin; // const pointer, non-const data
   byte* const m_end;   // const pointer, non-const data
   byte* m_pData;       // a non-const pointer, e.g. write-head

public:
   ByteBuffer(size_t byteBufferSize)
   :  m_begin(new byte[byteBufferSize],
      m_end(m_begin + byteBufferSize),
      m_pData(m_begin)
   {}

   ~ByteBuffer() { delete[] m_begin; }
};

Если я хочу, чтобы код сохранялся, было бы неплохо убедиться, что m_begin и m_end равны const, чтобы гарантировать, что все математические операции, касающиеся размера контейнера, будут правильными (и чтобы кто-то другой пришел в выигрыш не обновлять края случайно. Но недостатком является то, что я больше не могу инициализировать данные где-либо за пределами списка инициализации конструктора.

Редко, как std::bad_alloc, может быть, я не уверен, что звонить new[], как я делал выше, хорошая идея. Итак, мой вопрос: как бы мы справились с желанием const указателей на память для управления в классе?

Я бы хотел, чтобы конструктор сделал что-то похожее на это:

: m_begin(nullptr), m_end(nullptr), m_pData(nullptr)
{
     // initialize here
     // if initialization is successful, set const & non-const ptrs
}

Я обдумываю это? Делает ли m_begin a const unique_ptr устранение всех проблем инициализации?

1 Ответ

0 голосов
/ 04 июля 2018

Приведенный выше код, по сути, является правильным способом написания. Однако вместо этого вы можете использовать std::vector, поскольку вам не придется явно управлять памятью.

Если факт, std::vector уже определит begin() и end() (что заменит m_pData в вашем коде), и вам действительно не понадобится замена m_end, поскольку вы сами не управляете памятью.

Эквивалентом будет begin() + capacity(), но поскольку элемент не инициализируется после end, вам все равно не следует обращаться к ним (возможно, неопределенное поведение с языковой точки зрения).

В противном случае возможны небольшие улучшения, такие как:

  • Избегайте m_ для имени переменной члена. Не очень полезно.
  • Избегайте префикса p для указателя на данные.
  • В списке инициализации поставьте запятую в начале строки (как вы уже сделали :) вместо конца предыдущей строки. Это позволяет добавлять, удалять, перемещать или комментировать элементы, когда это делается таким образом.
  • Если вы не хотите, чтобы ваш объект был перемещаемым или копируемым, предпочтительно явно указать его:

    public:
        ByteBuffer(const ByteBuffer &) = delete;
        ByteBuffer(ByteBuffer &&) = delete;
        ByteBuffer &operator=(const ByteBuffer &) = delete;
        ByteBuffer &operator=(ByteBuffer &&) = delete;
    

В качестве альтернативы, вы также можете использовать std::unique_ptr, и тогда вам не придется освобождать память самостоятельно. Это может быть полезно, когда вашему классу нужно сделать несколько выделений или когда после выделения в конструкторе может возникнуть исключение.

Лучшее решение действительно зависит от того, что вы хотите сделать с вашим классом ByteBuffer или даже от того, действительно ли вам нужен такой класс (как вы могли бы вместо этого использовать std::vector напрямую или сделать простой псевдоним, такой как:

using ByteBuffer = std::vector<unsigned char>;

Если это так, byte typedef может быть не очень полезным, если вы используете auto для своих циклов и итераторов.

Говоря о const, я бы порекомендовал использовать его для предотвращения нежелательных изменений. Однако иногда вам нужно сбросить эти указатели на nullptr во время уничтожения довольно сложных классов, где может быть некоторое взаимное уничтожение, например.

Таким образом, имея простые классы, которые уважают SRP (принцип единой ответственности), вы по существу избегаете подобных проблем.

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