在修改/允许修改引用值而不触及字段本身的方法上使用或不使用 const

Posted

技术标签:

【中文标题】在修改/允许修改引用值而不触及字段本身的方法上使用或不使用 const【英文标题】:Using or not using const on methods that modify / allow the modification of referenced values without touching the fields themselves 【发布时间】:2015-02-16 11:47:57 【问题描述】:

我不确定是否完全理解用于类方法的 const 关键字背后的哲学。

我一直认为,在类方法的签名中使用 const 关键字意味着该方法不会修改所调用对象的字段。 但是,当我们使用例如向量时,运算符 [] 的重载不是 const :

whateverT & vector<whateverT>::operator [] ( size_t pos )

无论你对给定的引用做什么,它都不会对向量的字段进行任何修改,即使引用的项目被修改了。

另一个例子:

template<class T> class Array

    T * _items;

    T & operator [] ( size_t pos ) const
    
        return _items[ pos ];
    

我可以在这里使用 const 关键字,因为 _items 的值没有被修改(无论我们如何处理它所指向的内容)。对于编译器这是正确的,但如果我访问其中一项并修改它,运算符 [] 将允许修改数组,即使它应该是 const,即不应该修改 " 数组的内容"

遇到这种情况应该怎么办?使用或不使用 const 关键字?

谢谢你:)

【问题讨论】:

编译器只坚持未改变的成员。然而,如果你有一个指针,指针本身可能不会被改变,而是它的内容。这在逻辑上打破了 const 的正确性,你有责任正确设计它(也许你想要打破) 【参考方案1】:

我认为这取决于对返回对象的修改对您的类意味着什么,考虑程序的逻辑及其对象模型,而不是纯粹的语法。假设您将Array 传递给接收const T &amp; 的函数,您是否希望该函数修改数组中的内容?如果函数交换两个元素,数组是否仍然相同?我认为在这种情况下答案是否定的,因此您应该将 const 引用返回到 const 版本的 operator[] 中的数组项。

【讨论】:

【参考方案2】:

vector 有两个操作符 [] T&amp; operator[] (size_type n)const T&amp; operator[] (size_type n) 的签名。第一个签名为您提供了对向量中可以修改的数据的引用。第二个函数为您提供了一个无法修改的常量引用。使用const,您可以在函数中的几个位置使用它

const T foo(int bar) // return a const T and take an int
T foo(const int bar) // return a T and take a const int
T foo(int bar) const // return a T and take an int but the function cannot change the state of the object

您还可以混合搭配这些功能。

【讨论】:

感谢您的回答。不完全是我想知道的。【参考方案3】:

这取决于您的设计决定。 Constness 旨在用作设计概念。它应该可以帮助您实现您认为“修改”和“非修改”访问的更高级别的概念。通过在类方法上正确使用const,您可以使“不可修改”的属性通过引用从一个对象传播到另一个对象。

“引用其他对象”可以表示至少两种不同的设计关系:

它可以用来实现聚合,在这种情况下,裁判被认为是裁判的一个组成部分。在这种情况下,您通常应该强制对完整对象的所有部分进行访问的常量性:如果引用者是常量,则引用者也应该被视为常量。您有责任通过适当地对类的接口进行 const 限定来强制执行后者。

为了支持该概念,您通常永远不会尝试在引用者的 const 方法中修改裁判(即使在形式上是可能的)。反之亦然:如果referrer 的某些方法修改了referee 的内容,则永远不应该声明该方法const(即使它在形式上是可能的)。另外,referrer 的const 方法永远不应该返回对referee 的非常量引用。

在这种情况下,外部世界甚至不应该知道聚合对象是通过引用存储的。这只是一个实现细节。对于外部世界来说,一切都应该看起来好像聚合对象是引用者的直接成员。

这正是您在std::vector 的情况下观察到的。它是这样设计的,以确保整个向量的常量传播到向量元素的常量。为了实现这一点,它实现了两个版本的运算符[]

reference       operator[]( size_type pos );
const_reference operator[]( size_type pos ) const;

它可以用来实现不意味着聚合的纯引用。在这种情况下,裁判被认为是一个完全独立的、不相关的对象。推荐人的常量不应该传播给推荐人。这种情况下可以在referrer的const方法中修改referee。可以从referer的const方法返回对referee的非常量引用。

这种设计的一个例子是标准的智能指针:指针引用指向指针,但指针的常量并不意味着指针的常量。比如std::shared_ptr只有一个版本的操作符*

T& operator*() const;

声明为const,但返回指向对象的非常量引用。

【讨论】:

好答案。我可能很想添加一些关于“const 意味着对std 的威胁安全” @Yakk: conststd 来说并不意味着线程安全。在一个线程中调用 const 成员而另一个线程正在修改容器是未定义的行为。 @mooingduck 定义了两个const 方法同时被两个不同线程调用的行为。同样,如果您在 std 容器中,它们从两个不同的线程调用 const 合格的方法应该是公平的游戏。通过说出const 并将自己置于一个标准容器中,您就能够在多个线程中被调用做出“一定的保证”,作为交换,std 容器对它自己的接口做出一定的线程安全保证( const 和其他一些)。 查看这个堆栈溢出问题:***.com/questions/14127379/… -- 精辟的“const 意味着线程安全”确实需要澄清。这并不意味着同步,它意味着(在某种意义上)“我保证线程安全”。 const 对象应该可以被所有公开接口中的多个线程使用。如何实现这取决于对象的程序员,而不是语言。 (您可以违反此规则,但在与std 谈论此类对象时要小心)【参考方案4】:

使用 const 定义 const 方法意味着您希望该方法始终将“this”指针视为指向常量对象的指针。

所以,如果我正确解释了您的问题,那么,由于您没有接触数据成员,因此是否实现 const 方法并不重要。但是,如果您偏执关心维护并且您希望确保该方法永远不会修改数据成员,那么您应该将其设为常量方法。可能这就是您正在寻找的哲学。

【讨论】:

以上是关于在修改/允许修改引用值而不触及字段本身的方法上使用或不使用 const的主要内容,如果未能解决你的问题,请参考以下文章

在 MongoDB 中搜索任何字段的值而不显式命名它

在 SQL 查询中使用文本字段值而不提交它

Postgresql创建视图后修改基础表的问题

如何在excel中找到多个最小值而不重复?

在 SwiftUI 中捕获文本字段值而不按返回键 [重复]

C#本质论读书笔记