【问题标题】:Const correctness of pointers against safe checks during initialization初始化期间针对安全检查的指针的 const 正确性
【发布时间】:2018-07-03 22:39:32
【问题描述】:

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

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 是否会以某种方式消除所有初始化问题?

【问题讨论】:

  • 拥有 const 数据成员的 IMO 麻烦多于其价值;例如该类不能被赋值。
  • 在你的最终代码块中,重要的问题是如果初始化成功,你想做什么?
  • @M.M:说得好。
  • @ilzxc "我想以某种方式渗透错误条件" - 您只需让错误发生并让构造函数的调用者根据需要处理它即可.如您所示,没有理由不使用成员初始化列表。如果您希望构造函数对分配错误做出反应(但不抑制),您可以随时使用 function-try-block
  • 您希望在初始化失败时引发异常。如果您允许使用 nullptr 创建类,那么每次使用这些类之一时,您都必须进行空指针检查并在它们失败时采取措施,这会使您的代码复杂化。

标签: c++ constructor constants


【解决方案1】:

上面的代码基本上是正确的写法。但是,您可以考虑改用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>;

事实上,如果您将auto 用于循环和迭代器,byte typedef 甚至可能没有那么有用。

谈到const,我通常建议使用它来防止不希望的更改。但是,有时您需要在销毁有些复杂的类期间将这些指针重置为nullptr,例如可能会发生一些相互破坏。

因此,通过拥有尊重 SRP(单一职责原则)的简单类,您基本上可以避免此类问题。

【讨论】:

  • 我建议避免主动提出批评,尤其是在不确定的情况下。
  • 匈牙利符号是公司政策,变量对齐和构造函数初始化列表的间距也是。除了我有疑问的事情外,我从课堂上删除了所有内容。为什么你会为此用例推荐std::vector 而不是std::array,这让我感到困惑。 (我没有使用向量,因为我希望我的内存足够静态,以保证 constness 的开始和结束......)
  • 如果你不想改进你的编码......那么这取决于你。但是,即使您必须使用匈牙利符号等过时的东西进行编码,您也不必在网络上使用。至少,您应该关心现代实践并帮助您提高声誉。
  • 如果你举个例子,尺寸是动态的(传递给构造函数),所以 std::array 将不适用,因为在编译时需要知道尺寸。 end常量 没有多大意义,因为您的实际大小会有所不同(即 m_pData)。
猜你喜欢
  • 2016-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多