Eu tenho uma classe como esta:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
// more stuff
};
Normalmente, eu quero padrão (zero) inicializar a counts
matriz como mostrado.
Em locais selecionados identificados por criação de perfil, no entanto, eu gostaria de suprimir a inicialização do array, porque sei que o array está prestes a ser substituído, mas o compilador não é inteligente o suficiente para descobrir isso.
O que é uma maneira idiomática e eficiente de criar um construtor zero-arg "secundário"?
Atualmente, estou usando uma classe de tag uninit_tag
que é passada como argumento fictício, assim:
struct uninit_tag{};
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
event_counts(uninit_tag) {}
// more stuff
};
Então chamo o construtor no-init como event_counts c(uninit_tag{});
quando quero suprimir a construção.
Estou aberto a soluções que não envolvam a criação de uma classe fictícia ou que são mais eficientes de alguma forma etc.
fonte
Respostas:
A solução que você já tem está correta e é exatamente o que eu gostaria de ver se estivesse revendo seu código. É o mais eficiente possível, claro e conciso.
fonte
uninit_tag
sabor em todos os lugares em que quero usar esse idioma. Eu esperava que já houvesse algo como esse tipo de indicador, talvez dentrostd::
.no_init
e a usaria em todas as minhas classes onde for necessário.std::piecewise_construct_t
estd::in_place_t
. Nenhum deles parece razoável para usar aqui. Talvez você queira definir um objeto global do seu tipo para usar sempre, para que você não precise de chaves em todas as chamadas de construtores. O STL faz isso comstd::piecewise_construct
forstd::piecewise_construct_t
.Se o corpo do construtor estiver vazio, ele poderá ser omitido ou padronizado:
A inicialização padrão
event_counts counts;
deixarácounts.counts
não inicializada (a inicialização padrão não é operacional aqui) e a inicialização doevent_counts counts{};
valor valorizará a inicializaçãocounts.counts
, preenchendo-a efetivamente com zeros.fonte
int i;
aceitamos que não seja inicializado com zero. Talvez devêssemos também aceitar queevent_counts counts;
não foi inicializado com zero e fazerevent_counts counts{};
nosso novo padrão.Eu gosto da sua solução. Você também pode ter considerado struct e variável estática aninhada. Por exemplo:
Com a variável estática, a chamada do construtor não inicializado pode parecer mais conveniente:
Obviamente, você pode introduzir uma macro para salvar a digitação e torná-la mais um recurso sistemático
fonte
Eu acho que um enum é uma escolha melhor do que uma classe de tag ou um bool. Você não precisa passar uma instância de uma estrutura e fica claro pelo chamador qual opção você está recebendo.
Em seguida, a criação de instâncias fica assim:
Ou, para torná-lo mais parecido com a abordagem de classe de tag, use uma enumeração de valor único em vez da classe de tag:
Depois, existem apenas duas maneiras de criar uma instância:
fonte
event_counts() : counts{} {}
counts
incondicionalmente, mas apenas quandoINIT
estiver definido.bool
eenum
são decentes, mas temos que estar cientes que o uso de parâmetro em vez de sobrecarga tem um pouco diferente sombra semântica. No primeiro, você parametriza claramente um objeto, portanto, a postura inicializada / não inicializada se torna seu estado, enquanto a passagem de um objeto tag para o ctor é mais como pedir à classe que faça uma conversão. Portanto, a IMO não é uma questão de escolha sintática.event_counts() : counts{} {}
.counts
é inicializada por, astd::fill
menos queNO_INIT
seja solicitado. Adicionar o construtor padrão, como você sugere, criaria duas maneiras diferentes de fazer a inicialização padrão, o que não é uma boa idéia. Eu adicionei outra abordagem que evita o usostd::fill
.Você pode considerar uma inicialização em duas fases para sua classe:
O construtor acima não inicializa a matriz para zero. Para definir os elementos da matriz como zero, é necessário chamar a função de membro
set_zero()
após a construção.fonte
std::function
como argumento construtor algo semelhante aoset_zero
argumento padrão. Você passaria uma função lambda se desejar uma matriz não inicializada.Eu faria assim:
O compilador será inteligente o suficiente para pular todo o código quando você usar
event_counts(false)
, e você poderá dizer exatamente o que quer dizer, em vez de tornar a interface da sua classe tão estranha.fonte
event_counts(false)
, o que isso significa? Você não tem idéia sem voltar e olhar o nome do parâmetro. Melhor pelo menos usar uma enumeração ou, nesse caso, uma classe sentinela / tag, conforme mostrado na pergunta. Então, você obtém uma declaração mais parecida comevent_counts(no_init)
, o que é óbvio para todos em seu significado.event_counts(bool initCountr = true)
.boost::parameter
e chamadaevent_counts(initCounts = false)
para facilitar a leituraevent_counts(bool initCounts = true)
na verdade , é um construtor padrão, pois todos os parâmetros têm um valor padrão. O requisito é apenas que seja possível chamar sem especificar argumentos,event_counts ec;
não se importa se é sem parâmetro ou usa valores padrão.Eu usaria uma subclasse apenas para economizar um pouco de digitação:
Você pode se livrar da classe fictícia alterando o argumento do construtor não inicializado para
bool
ouint
ou algo assim, pois ele não precisa mais ser mnemônico.Você também pode trocar a herança e definir
events_count_no_init
com um construtor padrão, como o Evg sugerido na resposta, e depois terevents_count
a subclasse:fonte
event_counts
, quero que seja do tipoevent_count
, nãoevent_count_uninitialized
, então devo cortar direto na construçãoevent_counts c = event_counts_no_init{};
, o que acho que elimina a maior parte da economia na digitação.event_count_uninitialized
objeto é umevent_count
objeto. Esse é o ponto principal da herança, eles não são tipos completamente diferentes.ecu
aec
ela funciona, mas não o contrário. Ou, se você usar funções de modelo, eles são tipos diferentes e terminam com instanciações diferentes, mesmo que o comportamento acabe sendo idêntico (e às vezes não será, por exemplo, com membros estáticos do modelo). Especialmente com o uso pesadoauto
disso, pode definitivamente surgir e ser confuso: eu não gostaria que a maneira como um objeto fosse inicializado permanentemente refletida em seu tipo.