
Eu queria escrever este post em julho, mas não podia, oh ironia , decidir como chamá-lo. Bons termos me vieram à mente somente após a palestra de Kate Gregory na CppCon , e agora posso finalmente dizer como chamar funções.
Obviamente, existem nomes que não carregam informações, como int f(int x)
. Eles também não precisam ser usados, mas não é sobre eles. Às vezes acontece que parece que as informações no título estão completas, mas não há absolutamente nenhum benefício delas.
Exemplo 1: std :: log2p1 ()
No C ++ 20, várias novas funções para operações de bit foram adicionadas ao cabeçalho, entre outras std::log2p1
. É assim:
int log2p1(int i) { if (i == 0) return 0; else return 1 + int(std::log2(x)); }
Ou seja, para qualquer número natural, a função retorna seu logaritmo binário mais 1 e para 0 retorna 0. E essa não é uma tarefa da escola para o operador if / else, isso é realmente uma coisa útil - o número mínimo de bits nos quais esse valor se ajustará. Apenas adivinhar pelo nome da função é quase impossível.
Exemplo 2: std :: bless ()
Agora não será sobre o nome
Uma pequena digressão: em C ++, a aritmética de ponteiro funciona apenas com ponteiros para criar elementos de matriz. O que, em princípio, é lógico: no caso geral, o conjunto de objetos vizinhos é desconhecido e "qualquer coisa pode acontecer em dez bytes à direita da variável i
". Esse é um comportamento inequivocamente vago.
int obj = 0; int* ptr = &obj; ++ptr;
Mas essa restrição declara uma quantidade enorme de comportamento indefinido do código existente. Por exemplo, aqui está uma implementação simplificada de std::vector<T>::reserve()
:
void reserve(std::size_t n) {
Alocamos memória, movemos todos os objetos e agora tentamos garantir que os ponteiros indiquem para onde ir. Aqui estão apenas as últimas três linhas indefinidas, porque contêm operações aritméticas em ponteiros fora da matriz!
Obviamente, não é o programador quem deve culpar. O problema está no próprio padrão C ++, que declara que esse pedaço de código obviamente razoável é um comportamento indefinido. Portanto, P0593 sugere corrigir o padrão adicionando algumas funções (como ::operator new
e std::malloc
) a capacidade de criar matrizes conforme necessário. Todos os ponteiros criados por eles se tornarão magicamente ponteiros para matrizes e operações aritméticas podem ser executadas com eles.
Ainda não é sobre os nomes, espere um segundo.
Mas, às vezes, operações em ponteiros são necessárias ao trabalhar com memória que uma dessas funções não alocou. Por exemplo, a função deallocate()
trabalha essencialmente com memória morta, na qual não há objetos, mas ainda assim deve adicionar o ponteiro e o tamanho da área. Nesse caso, o P0593 ofereceu a função std::bless(void* ptr, std::size_t n)
(havia outra função lá, também chamada de bless
, mas não é isso). Não tem efeito em um computador físico da vida real, mas cria objetos para uma máquina abstrata que permitiria o uso da aritmética de ponteiros.
O nome std::bless
era temporário.
Então, o nome.
Em Colônia, o LEWG recebeu a tarefa de criar um nome para esta função. As opções implicitly_create_objects()
e implicitly_create_objects_as_needed()
foram propostas, porque é isso que a função faz.
Eu não gostei dessas opções.
Exemplo 3: std :: parcial_sort_copy ()
Exemplo retirado da apresentação de Kate
Existe uma função std::sort
, que classifica os elementos do contêiner:
std::vector<int> vec = {3, 1, 5, 4, 2}; std::sort(vec.begin(), vec.end());
Há também std::partial_sort
, que classifica apenas parte dos elementos:
std::vector<int> vec = {3, 1, 5, 4, 2}; std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
E ainda existe std::partial_sort_copy
, que também classifica parte dos elementos, mas ao mesmo tempo o contêiner antigo não muda, mas transfere os valores para o novo:
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 alega que std::partial_sort_copy
é um nome mais ou menos, e eu concordo com ela.
Nome da Implementação e Nome do Resultado
Nenhum dos nomes listados é, estritamente falando, incorreto : todos descrevem perfeitamente o que a função faz. std::log2p1()
realmente conta o logaritmo binário e adiciona um a ele; implicitly_create_objects()
cria objetos implicitamente e std::partial_sort_copy()
classifica parcialmente o contêiner e copia o resultado. No entanto, não gosto de todos esses nomes, porque são inúteis .
Nenhum programador se senta e pensa: “Gostaria de poder pegar o logaritmo binário e adicionar um a ele”. Ele precisa saber quantos bits o valor fornecido caberá e, sem êxito, procura nas docas algo como bit_width
. Quando ele chega ao usuário da biblioteca, o que o logaritmo binário tem a ver com ele, ele já escreveu sua implementação (e provavelmente perdeu a verificação de zero). Mesmo que std::log2p1
tenha sido um milagre no código, o próximo a ver esse código deve entender novamente o que é e por que é necessário. bit_width(max_value)
não teria esse problema.
Da mesma forma, ninguém precisa “criar objetos implicitamente” ou “classificar parcialmente uma cópia de um vetor” - eles precisam reutilizar a memória ou obter os 5 maiores valores em ordem decrescente. Algo como recycle_storage()
(que também foi sugerido como o nome std::bless
) e top_n_sorted()
seria muito mais claro.
O Kate usa o termo nome de implementação para std::partial_sort_copy()
, mas também se encaixa em outras duas funções. A implementação de seu nome é realmente descrita perfeitamente. Isso é apenas o usuário precisa do nome do resultado - o que ele obtém chamando a função. Para a estrutura interna dela, ele não se importa, ele só quer descobrir o tamanho em bits ou reutilizar a memória.
Nomear uma função com base em suas especificações significa criar do nada um mal-entendido entre o desenvolvedor da biblioteca e seu usuário. Você deve sempre lembrar quando e como a função será usada.
Isso parece brega, sim. Mas, julgando por std::log2p1()
, isso está longe de ser óbvio para todos. Além disso, às vezes não é tão simples.
Exemplo 4: std :: popcount ()
std::popcount()
, como std::log2p1()
, em C ++ 20 propõe-se adicionar ao <bit>
. E esse, é claro, é um nome monstruosamente ruim. Se você não sabe o que essa função faz, é impossível adivinhar. A abreviação não é apenas confusa (existe pop no nome, mas pop / push não tem nada a ver com isso) - decifrar a contagem da população (contar a população? O número de populações?) Também não ajuda.
Por outro lado, std::popcount()
ideal para esta função porque chama a instrução popcount de instruções de montagem. Este não é apenas o nome da implementação - é sua descrição completa.
No entanto, neste caso, a diferença entre desenvolvedores de linguagem e programadores não é tão grande. Uma instrução que conta o número de unidades em uma palavra binária é chamada de popcount a partir dos anos sessenta. Para uma pessoa que sabe alguma coisa sobre operações de bits, esse nome é absolutamente óbvio.
A propósito, uma boa pergunta: você pensa em nomes que são convenientes para iniciantes ou os deixa familiares para os velhos?
Final feliz?
P1956 sugere renomear std::log2p1()
para std::bit_width()
. É provável que esta proposta seja aceita em C ++ 20. std::ceil2
e std::floor2
também serão renomeados para std :: bit_ceil () e std :: bit_floor () respectivamente. Os nomes antigos também não eram muito, mas por outros motivos.
O LEWG em Colônia não selecionou implicitly_create_objects[_as_needed]
nem recycle_storage
como o nome para std::bless
. Eles decidiram não incluir essa função no padrão. O mesmo efeito pode ser alcançado criando explicitamente uma matriz de bytes; portanto, eles dizem que a função não é necessária. Não gosto disso porque chamar std::recycle_storage()
seria mais legível. Outro std::bless()
ainda existe, mas agora é chamado start_lifetime_as
. Eu gosto disso. Deve entrar no C ++ 23.
É claro que std::partial_sort_copy()
não std::partial_sort_copy()
mais renomeado - com esse nome, ele entrou no padrão em 1998. Mas pelo menos std::log2p1
corrigido, e isso não é ruim.
Ao apresentar os nomes das funções, você precisa pensar sobre quem as usará e o que ele deseja delas. Como Kate disse, nomear exige empatia .