【问题标题】:C++11 const correctness for raw pointer getter原始指针获取器的 C++11 const 正确性
【发布时间】:2014-12-19 02:17:59
【问题描述】:

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

假设我们有一个类 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 的指针,我们将提供一个 const 的 getter,但返回指向 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;
};

【问题讨论】:

  • 使用pointer/smart_pointer时,由你来选择你想要的返回类型的限制。
  • 不,您不会提供两个版本的 getter。只有const-qualified getter 和非const-qualified setter。

标签: c++ pointers c++11 constants


【解决方案1】:

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

为什么?除非有一些额外的所有权从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 上重载它?

【讨论】:

  • 如果 m_b 恰好是 nullptr,在您的示例中会发生什么?
  • 您是否建议std::make_unique&lt;B&gt;() 可以返回null?或者您是否正在考虑A 的构造函数接受std::unique_ptr&lt;B&gt; 参数的情况?无论如何,如果在后一种情况下 m_b 以 null 结束,那么调用任一 A::GetB() 重载都会崩溃,可能会触发分段错误。我不知道A 或其调用者是否应该与空的B 指针协同工作。
  • 我正在考虑A 的构造函数将接受std::unique_ptr&lt;B&gt; 作为初始化参数的情况(无论是在ctor 中还是通过setter)。使用原始 ptr,客户端能够对GetB 的返回值执行空检查。参考是不可能的。我会在您的示例中添加一个bool hasB() 方法到A,它检查m_b 是否为nullptr
  • @seh 这对于可以安全实施的地方来说绝对是一个好点。然而,在促使我提出这个问题的情况下,我肯定必须处理 nullptr,正如 dureuill 指出的那样,这意味着通过引用返回将是一个坏主意。我试图通过在构造函数中传递 unique_ptr 来明确这一点,但我可能应该明确说明它:)
  • 是的,如果你想适应 A::m_b 为空,那么你需要Elements of Programming一书所说的定义空间谓词 :指示m_b 是否填充了有效实例的函数。或者,通过返回指针,您允许调用者使用语言本身内置的谓词检查指针是否为空。
【解决方案2】:

虽然我没有太多的权限来回答设计问题,但我会使用类似于 @seh 的 answer 中提出的方法,另外还有一种方法来检查 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 中的一个常见约定是 raw pointers can be used to represent non-owning pointers。不过,最终这必须在文档中说明。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-30
      相关资源
      最近更新 更多