C++ C++;11原始指针getter的常量正确性

C++ C++;11原始指针getter的常量正确性,c++,pointers,c++11,constants,C++,Pointers,C++11,Constants,我在C++11中遇到了一个关于常量正确性的小问题,我希望我能得到澄清——我想还没有人问过它 假设我们有一个类a,它包含我们想要公开的类B的实例。如果我们将其公开为引用,我们将提供getter的常量和非常量版本: class B; class A final { public: B& GetB() { return m_b; } const B& GetB() const { return m_b;

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

假设我们有一个类a,它包含我们想要公开的类B的实例。如果我们将其公开为引用,我们将提供getter的常量和非常量版本:

class B;

class A final
{
public:
    B& GetB() 
    {
        return m_b;
    }
    const B& GetB() const 
    {
        return m_b;
    }
private:
    B m_b;
};
但是,如果我们有一个指向B的指针,我们将提供一个getter,它是常量,但返回了指向B的非常量实例的指针副本。这是因为a不拥有B,它只拥有该指针,因此对B的外部修改不会改变a的状态。(注意:我是根据个人经验得出这个结论的;我从来没有发现任何东西明确说明这是应该如何工作的)

到目前为止,这一切都是有道理的,但如果A拥有唯一的指针(或共享指针),我们该怎么办to 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;
};
B类;
甲级决赛
{
公众:
A(标准::唯一性\u ptr b)
{
m_b=标准::移动(b);
}
B*GetB()
{
返回m_b.get();
}
常量B*GetB()常量
{
返回m_b.get();
}
私人:
std::唯一的ptr m_b;
};

对于所有权情况,如果成员函数可以允许修改逻辑上的
常量
对象的一部分,那将是不自然的,但这是一个设计问题

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


例如,考虑使用直接<代码>新的< /COD> > ING和原始指针实现的内部数组。当使用“<代码> STD::vector < /代码>替换存储时,您将不希望中断接口。因此,是否提供<代码> const <代码>非代码> const <代码>对访问函数M的规则是这样的。必须植根于设计级别,而不是语言允许的特定设计实现。

在这种情况下,如果您的设计在
a
按值持有a
B
时是正确的,那么我会在
a
std::unique\ptr>持有a
B
时使用相同的常量/非常量访问器

为什么?禁止从
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;
};
B类
{
// ...
};
甲级决赛
{
公众:
A():m_b(std::make_unique())
{}
B&GetB()
{
返回*m_b;
}
常量B&GetB()常量
{
返回*m_b;
}
私人:
std::唯一的ptr 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;
};
B类
{
// ...
};
甲级决赛
{
公众:
A(std::ptrb):m_b(std::move(b))
{}
布尔哈斯
{
返回m_b!=nullptr;
}
B&GetB()
{
返回*m_b;
}
常量B&GetB()常量
{
返回*m_b;
}
私人:
std::唯一的ptr m_b;
};

如果你真的不能没有指针(遗留API),那么它更像是一条灰色的线
,因为C++11中的一个常见约定是这样。不过,这最终必须在文档中指出。

使用指针/smart\u pointer时,您可以选择所需返回类型的限制。不,您不会同时提供两个版本的getter。只有
常量
限定的getter和not
const
-合格的setter。如果m_b恰好是
nullptr
,在您的示例中会发生什么?您是否建议
std::make_unique()
可能返回null?或者您正在考虑
A
的构造函数接受
std::unique\u ptr
参数的情况吗?不管怎样,如果
m\u b
在后一种情况下结束为null,则调用
A::GetB()
重载将崩溃,可能触发分段错误。我不知道
a
-或其调用者是否应该与null
B
指针协同工作。我正在考虑
a
的构造函数在初始化时接受
std::unique\ptr
作为参数的情况(无论是在ctor中还是通过setters)。使用原始ptr,客户机能够对
GetB
的返回值执行空检查。这在引用中是不可能的。我想在您的示例中添加一个
bool hasB()
A
方法,用于检查
m_b
是否为
nullptr
@seh这对于可以安全实现的地方来说无疑是一个好的点。然而,在促使我提出问题的场景中,我肯定必须处理nullptr,正如dureuill所指出的,这意味着通过重新返回引用是个坏主意。我试图通过传递唯一的\u ptr来说明这一点
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;
};