web-dev-qa-db-fra.com

Façon idiomatique de distinguer deux constructeurs sans arg

J'ai une classe comme ça:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    // more stuff

};

Habituellement, je veux initialiser par défaut (zéro) le tableau counts comme indiqué.

À des emplacements sélectionnés identifiés par le profilage, cependant, je voudrais supprimer l'initialisation du tableau, car je sais que le tableau est sur le point d'être écrasé, mais le compilateur n'est pas assez intelligent pour le comprendre.

Quelle est une manière idiomatique et efficace de créer un constructeur zéro-arg "secondaire"?

Actuellement, j'utilise une classe de balises uninit_tag Qui est passée comme argument factice, comme ceci:

struct uninit_tag{};

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(uninit_tag) {}

    // more stuff

};

Ensuite, j'appelle le constructeur no-init comme event_counts c(uninit_tag{}); quand je veux supprimer la construction.

Je suis ouvert aux solutions qui n'impliquent pas la création d'une classe factice, ou qui sont plus efficaces d'une manière ou d'une autre, etc.

41
BeeOnRope

La solution que vous avez déjà est correcte, et c'est exactement ce que je voudrais voir si je révisais votre code. Il est aussi efficace que possible, clair et concis.

33
John Zwinck

Si le corps du constructeur est vide, il peut être omis ou par défaut:

struct event_counts {
    std::uint64_t counts[MAX_COUNTERS];
    event_counts() = default;
};

Alors initialisation par défautevent_counts counts; va partir counts.counts non initialisé (l'initialisation par défaut est un no-op ici), et initialisation de la valeurevent_counts counts{}; va initialiser counts.counts, en le remplissant efficacement de zéros.

8
Evg

J'aime ta solution. Vous pourriez également avoir considéré la structure imbriquée et la variable statique. Par exemple:

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

};

Avec une variable statique, l'appel du constructeur non initialisé peut sembler plus pratique:

event_counts e(event_counts::uninit);

Vous pouvez bien sûr introduire une macro pour économiser la frappe et en faire une fonctionnalité plus systématique

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

struct event_counts {
    UNINIT_TAG
}

struct other_counts {
    UNINIT_TAG
}
6
doc

Je pense qu'une énumération est un meilleur choix qu'une classe de balises ou un booléen. Vous n'avez pas besoin de passer une instance d'une structure et il est clair pour l'appelant quelle option vous obtenez.

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);
        }
    }
};

Ensuite, la création d'instances ressemble à ceci:

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

Ou, pour que cela ressemble davantage à l'approche de classe de balises, utilisez une énumération à valeur unique au lieu de la classe de balises:

struct event_counts {
    enum NoInit { NO_INIT };
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}
    explicit event_counts(NoInit) {}
};

Il n'y a alors que deux façons de créer une instance:

event_counts e1{};
event_counts e2{event_counts::NO_INIT};
3
TimK

Je le ferais comme ça:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(bool initCounts) {
        if (initCounts) {
            std::fill(counts, counts + MAX_COUNTERS, 0);
        }
    }
};

Le compilateur sera suffisamment intelligent pour ignorer tout le code lorsque vous utilisez event_counts(false), et vous aurez à dire exactement ce que vous voulez dire au lieu de rendre l'interface de votre classe si bizarre.

1
Matt Timmermans

Vous voudrez peut-être envisager une initialisation en deux phases pour votre classe:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() = default;

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

Le constructeur ci-dessus n'initialise pas le tableau à zéro. Pour mettre les éléments du tableau à zéro, vous devez appeler la fonction membre set_zero() après la construction.

1
眠りネロク

J'utiliserais une sous-classe juste pour économiser un peu de frappe:

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{}) {}
};

Vous pouvez vous débarrasser de la classe factice en changeant l'argument du constructeur non initialisé en bool ou int ou quelque chose, car il n'a plus besoin d'être mnémonique.

Vous pouvez également échanger l'héritage et définir events_count_no_init avec un constructeur par défaut comme Evg suggéré dans leur réponse, puis avoir events_count être la sous-classe:

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{} {}
};
1
Ross Ridge