C++11 原始指针获取器的常量正确性

C++11 const correctness for raw pointer getter

本文关键字:常量 正确性 获取 原始 指针 C++11      更新时间:2023-10-16

我在 C++11 中遇到了一个常量正确性的小问题,我希望我能得到澄清——我认为它还没有被问过!

假设我们有一个类 A,其中包含我们想要公开的类 B 的实例。如果我们将其公开为参考,我们将提供 getter 的 const 和非 const 版本:

class B;
class A final
{
public:
    B& GetB() 
    {
        return m_b;
    }
    const B& GetB() const 
    {
        return m_b;
    }
private:
    B m_b;
};
但是,如果我们只有一个指向 B 的指针,

我们将提供一个 getter,它是 const 但返回指向 B 的非 const 实例的指针副本。这是因为 A 不拥有 B,它只拥有指针,因此对 B 的外部修改不会改变 A 的状态。 (注意:我从个人经验中得出了这个结论;我从来没有找到任何明确说明这是应该如何工作的(

class B;
class A final
{
public:
    A(B* b) 
    {
        m_b = b;
    }
    A* GetB() const
    {
        return m_b;
    }
private:
    B* m_b;
};

到目前为止,这一切都是有道理的,但是如果 A 拥有指向 B 的唯一指针(或与此相关的共享指针(,我们该怎么办?A现在在逻辑上拥有B——即使不是字面意思。到目前为止,在将原始指针公开到共享指针时,我一直遵循上面的第二个示例,但是由于 A 在逻辑上拥有 B,我应该做一些与第一个示例更相似的事情吗?

class B;
class A final
{
public:
    A(std::unique_ptr<B> b)
    {
        m_b = std::move(b);
    }
    B* GetB()
    {
        return m_b.get();
    }
    const B* GetB() const
    {
        return m_b.get();
    }
private:
    std::unique_ptr<B> m_b;
};

在这种情况下,如果您的设计在A按值持有B时是正确的,那么当Astd::unique_ptr持有B时,我将使用相同的 const/non-const 访问器。

为什么?除非有一些额外的所有权A转让方式(例如移动分配操作员(,否则在这两种情况下,A都拥有一家将随之而生和消亡的B。在后一种情况下,B实例恰好在堆上单独分配并提供给构造函数是无关紧要的;它的寿命是相同的。

您的

示例还有另一种变体可能有助于解决您的犹豫不决,假设我们不需要处理A::m_b为空的可能性:

class B
{
  // ...
};
class A final
{
public:
  A() : m_b(std::make_unique<B>())
  {}
  B& GetB()
  {
    return *m_b;
  }
  const B& GetB() const
  {
    return *m_b;
  }
private:
  std::unique_ptr<B> m_b;
};

在这里,A显然拥有自己的B,并且没有提供转让所有权的方法。我不知道为什么我们要在这里分配B堆,但我们可以提出一个观点。(请注意,现在A::GetB()按引用而不是指针返回。有了这个例子,你仍然很难决定如何编写A::GetB()以及是否在 const 上重载它?

虽然我没有太多权限回答设计问题,但我会使用类似于@seh答案中提出的东西,并添加一种方法来检查 m_b 的空值:

class B
{
  // ...
};
class A final
{
public:
  A(std::ptr<B> b) : m_b(std::move(b))
  {}
  bool HasB()
  {
    return m_b != nullptr;
  }
  B& GetB()
  {
    return *m_b;
  }
  const B& GetB() const
  {
    return *m_b;
  }
 private:
   std::unique_ptr<B> m_b;
};

如果你真的离不开指针(遗留API(,那么它更像是一条灰线。我会声明const B* GetB() constB* GetB(),因为 C++11 中的一个常见约定是原始指针可用于表示非拥有指针。不过,最终必须在文档中指出这一点。

对于所有权情况,如果成员函数可以允许修改逻辑const对象的一部分,恕我直言,这将是不自然的,但这是一个设计问题。

但是,请注意,原始指针可用于实现所有权。

例如,考虑使用直接new -ing 和原始指针实现的内部数组。您不希望接口在/如果将其替换为用于处理存储的std::vector时会中断。因此,是否提供const/非const对访问器函数的规则必须植根于设计级别,而不是语言允许给定的设计实现。