一个返回非常量成员变量指针的常量成员函数,这样做有什么好处?

8

我在一个大型协作项目中工作(其中大部分都是非专业程序员,我也是其中之一)。我经常看到以下情况的例子:

void T::DoChanges(I i); // will make changes to internal structure of T (non-const)
V T::GetValue();

class A
{
private:
  T* fMember;
public:
  A(); 
  T* GetMember() const {return fMember;}
}

应用场景

,其中一个例子是:
A a;
I i;
a->GetMember()->DoChanges(i);
V v = a->GetMember()->GetValue();

当我学习编程课程时,我被灌输了一个观念,即const不仅指类实例的位结构,还包括内部逻辑结构。这种做法违反了这个观念。根据这种哲学,成员函数应采取以下形式:

T* GetMember() {return fMember;}
const T* GetMember() const {return fMember;}

我听说有些人认为const应该严格只用于成员变量,这是使用C++术语的严格说法。有什么理由支持这种做法吗?
5个回答

4

将成员函数声明为const可以向用户表明该函数不会修改任何类成员。
返回的成员可能是const,也可能不是,但将成员函数声明为const可以让类的用户清楚地了解该函数的行为。


1
我认为指示的清晰度在很大程度上取决于作者和用户之间的理解,即const是否仅涵盖严格属于C++术语意义下的成员,还是它也涵盖了类型A的逻辑结构中的成员。 - qonf
1
@qonf:该“理解”必须通过接口表达,并且通过声明成员函数为“const”成员函数来完成。 - Alok Save
不,我尊重地不同意。它必须以其他方式进行通信,例如文档。假设作者编写了一个声明为const的函数A,返回指向A逻辑结构中非const成员的指针。假设用户Tom通过在代码的某个地方声明A的const实例来使用A,因为他认为const是指逻辑结构保持不变。然后在稍后的代码中,Tom修改了指针所指向的实例。现在Tom无意中违反了他先前的假设,并可能引入了错误。 - qonf
声明一个方法为const所带来的唯一假设是该对象在执行该方法时不会被修改。并没有假设调用者不会修改非const指针的内容。 - user334856

2

这主要取决于设计,但就您的情况而言,我不明白为什么您需要两个原型。

您需要问自己的问题是,您是否希望人们通过get更改您的T成员变量。

如果是这样,您的原型应该如下所示:

T* GetMember() const {return fMember;}

如果您不需要修改返回值,请将其声明为const:
const T* GetMember() const {return fMember;}

该函数不一定需要是const,但考虑到没有问题也可以这样做。话虽如此,你会在不同的非const成员函数中调用该函数吗?因为如果你打算这样做,它将无法工作,因为它不是const
由于你只返回一个成员指针而不实际更改任何内容,我建议将该函数设置为const。这也作为对项目中其他人的指令,表明该函数在内部不修改任何内容。
返回值是否应该是const完全取决于设计。

嗨,感谢您的评论。但是我认为您可能说错了话或完全没有理解我的观点。您说:“由于您仅返回成员的指针而不实际更改任何内容...”。首先,该指针指向A的逻辑结构的成员,而不是按照标准C ++术语的成员。允许A的用户修改A实例的逻辑结构,我不会称之为“实际上没有更改任何内容”。这是我帖子的核心问题,除非我误解了您的帖子,否则您根本没有涉及到这个问题。请原谅我的坦率。 - qonf
@qonf 没有错过任何一点,我也在谈论 A 的成员指针。你通过调用 GetMember() 不会改变它,你只是获取指针...当然之后你可以改变它。 - Luchian Grigore
GetMember在技术上并不返回成员。它返回成员的副本,这是指向T的指针,从技术上讲,它不是成员。 - qonf

0

我本来想回答这是一个设计问题,你可以选择是否满意可变访问器(在这种情况下,你选择第一个 const 版本),或者如果你想禁止常量引用修改指针(在这种情况下,你选择第二个、两个重载版本)。

但仔细想想,我认为你的问题表明 getter 和 setter 总体上是一个不好的主意。你不应该只是盲目地转发类成员(在这种情况下,你可能会直接将它们作为公共成员)。相反,你的类应该提供一个有意义的接口,抽象出实现细节。因此,理想情况下,你的类中存在原始指针不应该影响用户,而你的接口函数应该负责保证相关的逻辑常数性。

如果你选择放弃这些面向对象的设计原则,那么我想你就得自己承担风险,做出最适合你整体设计的决定。


嗨,谢谢你的回答。你提出了一个很好的论点,但是如果你的对象需要经常进行大量修改,使用那种哲学会付出很高的代价。 - qonf
假设在我的例子中,T 在可修改性方面有 10 个自由度,A 的用户需要能够操纵其中的所有自由度。A 需要拥有 T 的接口副本(10 个函数,假设没有第三方类型来促进信息传输),或者 A 需要获取并提供复制品/引用。第一种选择在实现/维护方面成本很高。如果 T 的赋值运算符成本很高,则第二个选项在计算上也很昂贵。 - qonf
获取并提供T的副本/引用 - qonf
@qonf:可以说,这样的类具有过多的职责(“上帝类反模式”)。这只是一种直觉感觉,您可能有很好的理由来设计这样的类,因此请将其视为纯哲学思考,可能适用于您的情况,也可能不适用。 - Kerrek SB
我的合作伙伴进行复杂结构数据的分析。这意味着处理大量独立值。创建能够处理大量值的类型,以及创建能够处理这些类型的类型,实际上是不可避免的,至少在我看来是这样。此外,这些类型的复制/赋值变得非常昂贵。 - qonf

0

可以说,类的逻辑结构与所指向的内容的更改无关。例如,考虑一个链表节点。只要下一个(可能还有前一个)指针相同,节点就是不变的-但是节点并不在乎您是否更改了下一个节点。此外,T本身可能只是一个设计上不可变的对象,因此const T是毫无意义的区别。


在我考虑的情况下,逻辑结构取决于所指向的内容,而T是可变的。如果两者都不成立,我的问题就没有意义了,尽管我猜这可能是你的观点。 - qonf

0

这是完全可以的,并且在 T* const 的情况下同样有用。

考虑一个被多个线程访问的哈希表。在另一个线程访问对象时,内部结构需要保持不变,但这并不意味着哈希表只能包含常量指针。(锁定单个对象比锁定整个数据结构更具可扩展性。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接