*static*成员函数的常量和非常量版本

const and non-const versions of *static* member functions

本文关键字:常量 非常 版本 static 成员 函数      更新时间:2023-10-16

我有两个版本的相同静态成员函数:一个是指向常量参数的指针,另一个是非常量参数的指示器。我想避免代码重复
在阅读了一些堆栈溢出问题后(这些问题都是关于非静态成员函数的),我想到了这个:

class C {
private:
  static const type* func(const type* x) {
    //long code
  }
  static type* func(type* x) {
      return const_cast<type*>(func(static_cast<const type*>(x)));
  }
public:
  //some code that uses these functions
};

(我知道处理指针通常是个坏主意,但我正在实现一个数据结构。)

我在libstdc++中发现了一些代码,如下所示:
注意:这些不是的成员功能

static type* local_func(type* x)
{
  //long code
}
type* func(type* x)
{
  return local_func(x);
}
const type* func(const type* x)
{
  return local_func(const_cast<type*>(x));
}

在第一种方法中,代码位于一个函数中,该函数将指针指向常量参数
在第二种方法中,代码位于一个函数中,该函数将指针指向非常数参数
通常应使用哪种方法?两者都正确吗?

最重要的规则是接口函数(公共方法、除详细名称空间中的函数之外的自由函数等)不应丢弃其输入的常量。Scott Meyer是最早讨论使用const_cast防止重复的人之一,这里有一个典型的例子(如何消除类似const和非const成员函数之间的代码重复?):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;

};

这指的是实例方法,而不是静态/自由函数,但原理是相同的。您注意到,非常量版本添加了const来调用另一个方法(对于实例方法,this指针是输入)。它在结尾处驱散了惊愕;这是安全的,因为它知道原始输入不是常量。

以另一种方式实现这一点将是极其危险的。如果您将接收到的函数参数的constness强制转换为,那么如果传递给您的对象实际上是const,那么您将在UB中承担很大的风险。也就是说,如果你调用任何实际改变对象的方法(这很容易意外地完成,因为你已经消除了constness),你可以很容易地得到UB:

C++标准,第5.2.11/7节【const cast】

[注意:根据对象的类型,通过指针、左值或指向数据成员的指针的写操作由强制转换const限定符的const_cast可能会产生未定义的行为--尾注]

它在私有方法/实现函数中并没有那么糟糕,因为也许你仔细控制了它的调用方式/时间,但为什么要这样做呢?没有好处更危险。

从概念上讲,通常情况下,当您有同一函数的常量和非常量版本时,您只是传递对象的内部引用(vector::operator[]是一个典型的例子),而不是实际更改任何内容,这意味着无论以何种方式编写它都是安全的。但丢弃输入的常量仍然更危险;尽管你自己可能不太可能把它搞砸,但想象一下,在一个团队环境中,你用错误的方式写它,它工作得很好,然后有人改变实现来改变一些东西,给你UB。

总之,在许多情况下,这可能没有实际的区别,但有一种正确的方法比其他方法更好:常量添加到输入输出中删除常数。

实际上我以前只见过你的第一个版本,所以根据我的经验,这是一个更常见的习惯用法。

在我看来,第一个版本似乎是正确的,而如果(A)将实际的const对象传递给函数,并且(B)long code写入该对象,则第二个版本可能会导致未定义的行为。考虑到在第一种情况下,编译器会告诉你是否要写入对象,我永远不会推荐选项2。不过,你可以考虑使用一个接受/返回const的独立函数。