
Quería escribir esta publicación en julio, pero no podía, oh ironía , decidir cómo llamarla. Me vinieron a la mente buenos términos después de la charla de Kate Gregory en CppCon , y ahora finalmente puedo decirte cómo llamar a las funciones.
Por supuesto, hay nombres que no llevan información en absoluto, como int f(int x)
. Tampoco necesitan ser utilizados, pero no se trata de ellos. A veces sucede que parece que la información en el título está completa, pero no tiene ningún beneficio.
Ejemplo 1: std :: log2p1 ()
En C ++ 20, se agregaron varias funciones nuevas para operaciones de bit al encabezado, entre otras std::log2p1
. Se ve así:
int log2p1(int i) { if (i == 0) return 0; else return 1 + int(std::log2(x)); }
Es decir, para cualquier número natural, la función devuelve su logaritmo binario más 1, y para 0 devuelve 0. Y esto no es un problema escolar para el operador if / else, esto es realmente algo útil: la cantidad mínima de bits en los que se ajustará este valor. Solo adivinarlo por el nombre de la función es casi imposible.
Ejemplo 2: std :: bless ()
Ahora no será sobre el nombre
Una pequeña digresión: en C ++, la aritmética de puntero solo funciona con punteros para agrupar elementos. Lo cual, en principio, es lógico: en el caso general, el conjunto de objetos vecinos es desconocido y "cualquier cosa puede suceder en diez bytes a la derecha de la variable i
". Este es un comportamiento inequívocamente vago.
int obj = 0; int* ptr = &obj; ++ptr;
Pero tal restricción declara una gran cantidad de comportamiento de código indefinido existente. Por ejemplo, aquí hay una implementación simplificada de std::vector<T>::reserve()
:
void reserve(std::size_t n) {
Hemos asignado memoria, movido todos los objetos y ahora tratamos de asegurarnos de que los punteros indiquen a dónde ir. ¡Aquí están solo las últimas tres líneas que no están definidas, porque contienen operaciones aritméticas en punteros fuera de la matriz!
Por supuesto, no es el programador quien tiene la culpa. El problema está en el estándar C ++ en sí, que declara que este código obviamente razonable es un comportamiento indefinido. Por lo tanto, P0593 sugiere corregir el estándar agregando algunas funciones (como ::operator new
y std::malloc
) la capacidad de crear matrices según sea necesario. Todos los punteros creados por ellos se convertirán mágicamente en punteros de matrices, y se pueden realizar operaciones aritméticas con ellos.
Aún no se trata de los nombres, espera un segundo.
Pero a veces, se requieren operaciones en punteros cuando se trabaja con memoria que una de estas funciones no asignó. Por ejemplo, la función deallocate()
funciona esencialmente con memoria muerta, en la que no hay ningún objeto, pero aún debe sumar el puntero y el tamaño del área. Para este caso, P0593 ofreció la función std::bless(void* ptr, std::size_t n)
(había otra función allí, que también se llama bless
, pero no se trata de eso). No tiene ningún efecto en una computadora física de la vida real, pero crea objetos para una máquina abstracta que permitiría el uso de la aritmética de puntero.
El nombre std::bless
fue temporal.
Entonces, el nombre.
En Colonia, LEWG recibió la tarea de encontrar un nombre para esta función. Se propusieron las opciones implicitly_create_objects()
y implicitly_create_objects_as_needed()
, porque esto es lo que hace la función.
No me gustaron estas opciones.
Ejemplo 3: std :: partial_sort_copy ()
Ejemplo tomado de la presentación de Kate
Hay una función std::sort
, que ordena los elementos del contenedor:
std::vector<int> vec = {3, 1, 5, 4, 2}; std::sort(vec.begin(), vec.end());
También hay std::partial_sort
, que clasifica solo parte de los elementos:
std::vector<int> vec = {3, 1, 5, 4, 2}; std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
Y todavía hay std::partial_sort_copy
, que también clasifica parte de los elementos, pero al mismo tiempo el contenedor anterior no cambia, pero transfiere los valores al nuevo:
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 afirma que std::partial_sort_copy
es un nombre regular, y estoy de acuerdo con ella.
Nombre de implementación y nombre del resultado
Ninguno de los nombres enumerados es, estrictamente hablando, incorrecto : todos describen perfectamente lo que hace la función. std::log2p1()
realmente cuenta el logaritmo binario y le agrega uno; implicitly_create_objects()
crea implícitamente objetos, y std::partial_sort_copy()
ordena parcialmente el contenedor y copia el resultado. Sin embargo, no me gustan todos estos nombres, porque son inútiles .
Ningún programador se sienta y piensa: "Me gustaría poder tomar el logaritmo binario y agregarle uno". Necesita saber cuántos bits encajará el valor dado, y busca sin éxito en los muelles algo como bit_width
. Para cuando llega al usuario de la biblioteca, qué tiene que ver el logaritmo binario con él, ya escribió su implementación (y probablemente perdió la verificación de cero). Incluso si std::log2p1
resultó ser un milagro en el código, el siguiente en ver este código debería comprender nuevamente qué es y por qué es necesario. bit_width(max_value)
no tendría ese problema.
Del mismo modo, nadie necesita "crear objetos implícitamente" u "ordenar parcialmente la copia del vector"; necesitan reutilizar la memoria u obtener los 5 valores más grandes en orden descendente. Algo así como recycle_storage()
(que también se sugirió como el nombre std::bless
) y top_n_sorted()
sería mucho más claro.
Kate usa el término nombre de implementación para std::partial_sort_copy()
, pero también se ajusta a otras dos funciones. La implementación de su nombre se describe realmente perfectamente. Eso es solo que el usuario necesita el nombre del resultado, lo que obtiene al llamar a la función. Para su estructura interna, no le importa, solo quiere saber el tamaño en bits o reutilizar la memoria.
Nombrar una función basada en su especificación significa crear de la nada un malentendido entre el desarrollador de la biblioteca y su usuario. Siempre debe recordar cuándo y cómo se utilizará la función.
Eso suena cursi, sí. Pero a juzgar por std::log2p1()
, esto está lejos de ser obvio para todos. Además, a veces no es tan simple.
Ejemplo 4: std :: popcount ()
std::popcount()
, como std::log2p1()
, en C ++ 20 se propone agregar a <bit>
. Y esto, por supuesto, es un nombre monstruosamente malo. Si no sabe qué hace esta función, es imposible adivinar. La abreviatura no solo es confusa (hay pop en el nombre, pero pop / push no tiene nada que ver con eso), descifrar el recuento de la población (¿contar la población? ¿El número de poblaciones?) Tampoco ayuda.
Por otro lado, std::popcount()
ideal para esta función porque llama a la instrucción de ensamblaje popcount. Este no es solo el nombre de la implementación, es su descripción completa.
Sin embargo, en este caso, la brecha entre los desarrolladores de lenguaje y los programadores no es tan grande. Una instrucción que cuenta el número de unidades en una palabra binaria se llama popcount de los años sesenta. Para una persona que sabe algo sobre operaciones con bits, dicho nombre es absolutamente obvio.
Por cierto, una buena pregunta: ¿piensas en nombres que sean convenientes para los principiantes, o los dejas familiarizados para los viejos?
Final feliz?
P1956 sugiere cambiar el nombre de std::log2p1()
a std::bit_width()
. Es probable que esta propuesta sea aceptada en C ++ 20. std::ceil2
y std::floor2
también se renombrarán a std :: bit_ceil () y std :: bit_floor () respectivamente. Sus antiguos nombres tampoco eran muy, pero por otras razones.
LEWG en Colonia no seleccionó implicitly_create_objects[_as_needed]
ni recycle_storage
como el nombre de std::bless
. Decidieron no incluir esta función en el estándar en absoluto. Se puede lograr el mismo efecto creando explícitamente una matriz de bytes, por lo tanto, dicen, la función no es necesaria. No me gusta esto porque llamar a std::recycle_storage()
sería más legible. Todavía existe otro std::bless()
, pero ahora se llama start_lifetime_as
. Eso me gusta Debería ir a C ++ 23.
Por supuesto, std::partial_sort_copy()
ya no será renombrado; bajo este nombre ingresó al estándar en 1998. Pero al menos std::log2p1
reparado, y eso no está mal.
Al idear los nombres de las funciones, debe pensar en quién las usará y qué quiere de ellas. Como dijo Kate, nombrar requiere empatía .