我在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不是OWN 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;
};
答案 0 :(得分:3)
在这种情况下,如果您的设计在A
按值B
时是正确的,那么我会在A
时使用相同的const /非const访问器按B
保留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()
通过引用而不是指针返回。)在这个例子中,你是否仍然很难决定如何写{{1}}以及是否在const上重载它?
答案 1 :(得分:2)
虽然我没有太多权限回答设计问题,但我会使用类似于@ seh&#39; 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() const
和B* GetB()
,因为C ++ 11中的常见约定是raw pointers can be used to represent non-owning pointers。不过,最终必须在文档中说明这一点。
答案 2 :(得分:1)
对于所有权情况,如果成员函数可以允许修改逻辑const
对象的一部分,那么IMHO会不自然,但这是一个设计问题。
但是,请注意原始指针可以用于实现所有权。
例如,考虑使用直接new
和原始指针实现的内部数组。如果用std::vector
替换它来处理存储,你不会想要一个会破坏的接口。因此,是否提供const
/非const
对访问函数的规则必须植根于设计级别,而不是根据给定的语言允许实施设计。