
我想在7月份写这篇文章,但是, 讽刺的是 ,我无法决定该怎么称呼。 只有在凯特·格雷格里 ( Kate Gregory)在CppCon上发表讲话之后,我才想到好术语,现在我终于可以告诉您如何调用函数了。
当然,有些名称根本不携带任何信息,例如int f(int x)
。 它们也不需要使用,但这与它们无关。 有时,似乎标题中的信息已满,但绝对没有好处。
示例1:std :: log2p1()
在C ++ 20中,在标头中添加了几个用于位操作的新功能,其中包括std::log2p1
。 看起来像这样:
int log2p1(int i) { if (i == 0) return 0; else return 1 + int(std::log2(x)); }
也就是说,对于任何自然数,该函数将返回其二进制对数加1,对于0,它将返回0。对于if / else运算符而言,这不是学校问题,这确实是有用的事情-该值适合的最小位数。 仅通过函数名称猜测它几乎是不可能的。
示例2:std :: bless()
现在不再是名字
题外话:在C ++中,指针算法仅适用于指向数组元素的指针。 从原则上讲,这是合乎逻辑的:在通常情况下,相邻对象的集合是未知的,并且“在变量i
右边的十个字节中可能发生任何事情”。 这无疑是模糊的行为。
int obj = 0; int* ptr = &obj; ++ptr;
但是这样的限制声明了大量现有代码的不确定行为。 例如,这是std::vector<T>::reserve()
的简化实现:
void reserve(std::size_t n) {
我们已经分配了内存,移动了所有对象,现在尝试确保指针指示去向。 这只是最后三行未定义,因为它们包含对数组外部指针的算术运算!
当然,应该怪的不是程序员。 问题出在C ++标准本身,该标准将这段显然合理的代码声明为未定义行为。 因此, P0593建议通过添加一些函数(如::operator new
和std::malloc
)来根据需要创建数组的能力,以纠正标准。 它们创建的所有指针都将神奇地成为数组的指针,并且可以使用它们执行算术运算。
仍然没有名字,请稍等。
但是有时,当使用这些功能之一未分配的内存时,需要对指针进行操作。 例如, deallocate()
函数本质上适用于死存储器,在死存储器中根本没有对象,但是仍然必须加总指针和区域的大小。 在这种情况下,P0593提供了std::bless(void* ptr, std::size_t n)
函数std::bless(void* ptr, std::size_t n)
(那里还有另一个函数,也称为bless
,但这与它无关)。 它对现实生活中的物理计算机没有影响,但是它为抽象机创建对象,从而允许使用指针算术。
名称std::bless
是暂时的。
所以,名字。
在科隆,LEWG的任务是为此功能命名。 提出了“ implicitly_create_objects()
和“ implicitly_create_objects_as_needed()
”选项,因为这是函数的作用。
我不喜欢这些选项。
示例3:std :: partial_sort_copy()
凯特演讲中的例子
有一个功能std::sort
,它对容器的元素进行排序:
std::vector<int> vec = {3, 1, 5, 4, 2}; std::sort(vec.begin(), vec.end());
还有std::partial_sort
,它仅对部分元素进行排序:
std::vector<int> vec = {3, 1, 5, 4, 2}; std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
仍然有std::partial_sort_copy
,它也对部分元素进行排序,但是同时旧容器不会更改,而是将值转移到新容器中:
const std::vector<int> vec = {3, 1, 5, 4, 2}; std::vector<int> out; out.resize(3); std::partial_sort_copy(vec.begin(), vec.end(), out.begin(), out.end());
凯特(Kate)声称std::partial_sort_copy
是一个普通的名字,我同意她的看法。
实施名称和结果名称
严格来说,列出的名称中没有一个是错误的 :它们都完美地描述了该功能的作用。 std::log2p1()
真正计算二进制对数并加一个; implicitly_create_objects()
隐式创建对象, std::partial_sort_copy()
对容器进行部分排序并复制结果。 但是,我不喜欢所有这些名称,因为它们没有用 。
没有程序员坐下来思考:“我希望我可以取二进制对数并加一个”。 他需要知道给定值适合多少位,并且他在bit_width
搜索诸如bit_width
类的内容bit_width
。 当他到达库用户时,二进制对数与它有什么关系,他已经编写了实现(很可能错过了对零的检查)。 即使std::log2p1
在代码中被证明是一个奇迹,下一个看到此代码的人也应该再次理解它的含义以及为什么需要它。 bit_width(max_value)
不会有这样的问题。
同样,没有人需要“隐式创建对象”或“对向量的副本进行部分排序”-他们需要重用内存或以降序获得5个最大值。 像recycle_storage()
(也被建议为名称std::bless
)和top_n_sorted()
会更加清晰。
凯特(Kate)将术语实现名称用于std::partial_sort_copy()
,但它也适合其他两个函数。 确实很好地描述了其名称的实现。 只是用户需要结果的名称-他通过调用函数得到的结果。 对于她的内部结构,他不在乎,他只想找出位的大小或重用内存。
根据功能的规范来命名功能意味着要在库开发人员及其用户之间产生误解。 您必须始终记住何时以及如何使用该功能。
听起来很老套,是的。 但是从std::log2p1()
来看,这对每个人来说都不是显而易见的。 而且,有时候不是那么简单。
示例4:std :: popcount()
std::popcount()
类似, std::log2p1()
在C ++ 20中,建议添加到<bit>
。 当然,这是一个非常糟糕的名字。 如果您不知道此功能的作用,则无法猜测。 缩写不仅令人困惑(名称中包含pop,但pop / push与之无关)-解密人口计数(计算人口?人口数量?)也无济于事。
另一方面, std::popcount()
此函数的理想选择,因为它调用汇编指令popcount。 这不仅是实现的名称 -它是完整的描述。
但是,在这种情况下,语言开发人员和程序员之间的差距并不大。 从六十年代开始,对二进制字中的单位数进行计数的一条指令称为弹出计数。 对于一个对位操作一无所知的人来说,这样的名字绝对是显而易见的。
顺便说一句,一个好问题:您认为对于初学者来说方便的名称,还是让他们对oldfags熟悉?
幸福的结局?
P1956建议将std::log2p1()
重命名为std::bit_width()
。 该提议可能会在C ++ 20中被接受。 std::ceil2
和std::ceil2
也将分别重命名为std :: bit_ceil()和std :: bit_floor()。 他们的旧名字也不太好,但是出于其他原因。
科隆的LEWG既没有implicitly_create_objects[_as_needed]
选择implicitly_create_objects[_as_needed]
也没有选择recycle_storage
作为std::bless
的名称。 他们决定完全不在标准中包含此功能。 通过显式创建字节数组可以达到相同的效果,因此,他们说不需要该功能。 我不喜欢这样,因为调用std::recycle_storage()
更具可读性。 另一个std::bless()
仍然存在,但现在称为start_lifetime_as
。 我喜欢那样 它应该进入C ++ 23。
当然, std::partial_sort_copy()
不再重命名-以此名称在1998年进入标准。但是至少std::log2p1
固定的,这还不错。
在提出函数名称时,您需要考虑谁将使用它们以及他从函数中想要什么。 正如凯特(Kate)所说, 命名需要同理心 。