Nome da Implementação e Nome do Resultado


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) { //      auto new_memory = (T*) ::operator new(n * sizeof(T)); //    … //   auto size = this->size(); begin_ = new_memory; //   end_ = new_memory + size; //     end_capacity_ = new_memory + 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()); // vec == {1, 2, 3, 4, 5} 

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()); // vec == {1, 2, 3, ?, ?} ( ...4,5,  ...5,4) 

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()); // out == {1, 2, 3} 

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 .

Source: https://habr.com/ru/post/pt484860/


All Articles