c++ - Maneira linguística de distinguir dois construtores de zero-arg




performance constructor (5)

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 matriz de counts como mostrado.

Em locais selecionados identificados por criação de perfil, no entanto, eu gostaria de suprimir a inicialização da matriz, porque sei que a matriz está prestes a ser substituída, mas o compilador não é inteligente o suficiente para descobrir isso.

O que é uma maneira idiomática e eficiente de criar um construtor de zero-arg tão "secundário"?

Atualmente, estou usando uma classe de tag uninit_tag que é passada como um 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 eu chamo o construtor no-init como event_counts c(uninit_tag{}); quando eu 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.


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.


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.

struct event_counts {
    enum Init { INIT, NO_INIT };
    uint64_t counts[MAX_COUNTERS];

    event_counts(Init init = INIT) {
        if (init == INIT) {
            std::fill(counts, counts + MAX_COUNTERS, 0);
        }
    }
};

Em seguida, a criação de instâncias fica assim:

event_counts e1{};
event_counts e2{event_counts::INIT};
event_counts e3{event_counts::NO_INIT};

Eu gosto da sua solução. Você também pode ter considerado struct e variável estática aninhada. Por exemplo:

struct event_counts {
    static constexpr struct uninit_tag {} uninit = uninit_tag();

    uint64_t counts[MAX_COUNTS];

    event_counts() : counts{} {}

    explicit event_counts(uninit_tag) {}

    // more stuff

};

Com a variável estática, a chamada do construtor não inicializado pode parecer mais conveniente:

event_counts e(event_counts::uninit);

Obviamente, você pode introduzir uma macro para salvar a digitação e torná-la mais um recurso sistemático

#define UNINIT_TAG static constexpr struct uninit_tag {} uninit = uninit_tag();

struct event_counts {
    UNINIT_TAG
}

struct other_counts {
    UNINIT_TAG
}

Eu usaria uma subclasse apenas para economizar um pouco de digitação:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}
    event_counts(uninit_tag) {}
};    

struct event_counts_no_init: event_counts {
    event_counts_no_init(): event_counts(uninit_tag{}) {}
};

Você pode se livrar da classe dummy alterando o argumento do construtor que não está inicializando para bool ou int 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 Evg sugerido em suas respostas, e fazer com que events_count seja a subclasse:

struct event_counts_no_init {
    uint64_t counts[MAX_COUNTERS];
    event_counts_no_init() = default;
};

struct event_counts: event_counts_no_init {
    event_counts(): event_counts_no_init{} {}
};

Você pode considerar uma inicialização em duas fases para sua classe:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() = default;

    void set_zero() {
       std::fill(std::begin(counts), std::end(counts), 0u);
    }
};

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.





constructor