初始化期间针对安全检查的指针的正确性

时间:2018-07-03 22:39:32

标签: c++ constructor const

考虑一个分配内存块以供使用的类:

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_beginm_endconst,以确保有关容器大小的任何数学运算都是正确的(并且进来的人不会意外地更新边缘,但是缺点是我无法再在构造函数的初始化列表之外的任何地方初始化数据。

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设为const unique_ptr会使所有初始化麻烦都消失吗?

1 个答案:

答案 0 :(得分:0)

上面的代码本质上是编写它的正确方法。但是,您可以考虑改用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 (单一责任原则)的简单类,您基本上可以避免此类问题。