在修改/允许修改引用值而不触及字段本身的方法上使用或不使用 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 &
的函数,您是否希望该函数修改数组中的内容?如果函数交换两个元素,数组是否仍然相同?我认为在这种情况下答案是否定的,因此您应该将 const
引用返回到 const
版本的 operator[]
中的数组项。
【讨论】:
【参考方案2】:vector 有两个操作符 [] T& operator[] (size_type n)
和 const T& 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: const
对std
来说并不意味着线程安全。在一个线程中调用 const
成员而另一个线程正在修改容器是未定义的行为。
@mooingduck 定义了两个const
方法同时被两个不同线程调用的行为。同样,如果您在 std
容器中,它们从两个不同的线程调用 const
合格的方法应该是公平的游戏。通过说出const
并将自己置于一个标准容器中,您就能够在多个线程中被调用做出“一定的保证”,作为交换,std
容器对它自己的接口做出一定的线程安全保证( const
和其他一些)。
查看这个堆栈溢出问题:***.com/questions/14127379/… -- 精辟的“const 意味着线程安全”确实需要澄清。这并不意味着同步,它意味着(在某种意义上)“我保证线程安全”。 const
对象应该可以被所有公开接口中的多个线程使用。如何实现这取决于对象的程序员,而不是语言。 (您可以违反此规则,但在与std
谈论此类对象时要小心)【参考方案4】:
使用 const 定义 const 方法意味着您希望该方法始终将“this”指针视为指向常量对象的指针。
所以,如果我正确解释了您的问题,那么,由于您没有接触数据成员,因此是否实现 const 方法并不重要。但是,如果您偏执或关心维护并且您希望确保该方法永远不会修改数据成员,那么您应该将其设为常量方法。可能这就是您正在寻找的哲学。
【讨论】:
以上是关于在修改/允许修改引用值而不触及字段本身的方法上使用或不使用 const的主要内容,如果未能解决你的问题,请参考以下文章