İki sıfır arg yapıcısını ayırt etmenin deyimsel yolu


41

Ben böyle bir sınıf var:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    // more stuff

};

Genellikle varsayılan (sıfır) countsgösterildiği gibi dizi başlatmak istiyorum .

Profilleme ile belirlenen seçilen yerlerde, ancak, dizi üzerine yazmak üzere olduğunu biliyorum, ancak derleyici bunu anlamaya yetecek kadar akıllı değil, çünkü dizi başlatma bastırmak istiyorum.

Böyle bir "ikincil" sıfır arg yapıcısı oluşturmanın deyimsel ve etkili yolu nedir?

Şu anda, uninit_tagkukla bir argüman olarak geçirilen bir tag sınıfı kullanıyorum, şöyle :

struct uninit_tag{};

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(uninit_tag) {}

    // more stuff

};

Daha sonra inşaatı event_counts c(uninit_tag{});bastırmak istediğimde no-init yapıcısını çağırıyorum .

Sahte bir sınıfın oluşturulmasını içermeyen veya bir şekilde daha verimli olan çözümlere açıkım.


"dizinin üzerine yazmak üzere olduğunu biliyorum" derleyicinizin zaten sizin için bu optimizasyonu yapmadığından% 100 emin misiniz? durumda: gcc.godbolt.org/z/bJnAuJ
Frank

6
@Frank - Sorunuzun cevabı alıntıladığınız cümlenin ikinci yarısında mı? Bu soruya ait değildir, ancak çeşitli şeyler olabilir: (a) derleyici genellikle ölü depoları ortadan kaldıracak kadar güçlü değildir (b) bazen sadece öğelerin bir alt kümesinin üzerine yazılır ve bu, optimizasyon (ancak yalnızca aynı altküm daha sonra okunur) (c) bazen derleyici bunu yapabilir, ancak yöntem inline olmadığı için yenilebilir.
BeeOnRope

Sınıfınızda başka kurucular var mı?
NathanOliver

1
@Frank - eh, sizin durumunuz gcc'nin ölü depoları yok etmediğini gösteriyor ? Aslında, eğer beni tahmin etseydiniz, gcc'nin bu çok basit davayı doğru yapacağını düşünürdüm, ama eğer burada başarısız olursa, biraz daha karmaşık bir vaka hayal edin!
BeeOnRope

1
@uneven_mark - evet, gcc 9.2 bunu -O3'te yapar (ancak bu optimizasyon -O2, IME'ye kıyasla nadirdir), ancak önceki sürümler yoktu. Genel olarak, ölü mağaza ortadan kaldırılması bir şeydir, ancak çok kırılgandır ve derleyicinin ölü depoları aynı anda hakim mağazaları görmesi gibi her zamanki uyarılara tabidir. Yorumum Frank'in ne demeye çalıştığını açıklığa kavuşturmaktı.
BeeOnRope

Yanıtlar:


33

Zaten sahip olduğunuz çözüm doğrudur ve tam olarak kodunuzu gözden geçirip inceleyemeyeceğimi görmek istiyorum. Mümkün olduğunca verimli, açık ve özlüdür.


1
Sahip olduğum temel mesele, uninit_tagbu deyimi kullanmak istediğim her yerde yeni bir lezzet beyan edip etmemem. Belki böyle bir gösterge türü gibi bir şey olmasını umuyordum std::.
BeeOnRope

9
Standart kitaplıktan bariz bir seçenek yoktur. Bu özelliği istediğim her sınıf için yeni bir etiket tanımlamazdım - proje çapında bir no_initetiket tanımlar ve gereken tüm sınıflarımda kullanırdım.
John Zwinck

2
Ben standart kütüphane iterators ve böyle şeyler ve ikisini ayırt etmek için erkekçe etiketleri olduğunu düşünüyorum std::piecewise_construct_tve std::in_place_t. Hiçbiri burada kullanmak için makul görünmüyor. Belki her zaman kullanmak için türünüzün global bir nesnesini tanımlamak istersiniz, bu nedenle her yapıcı çağrısında parantezlere ihtiyacınız yoktur. STL bunu std::piecewise_constructiçin yapar std::piecewise_construct_t.
n314159

Mümkün olduğunca verimli değildir. Örneğin AArch64 çağrı kuralında, etiketin devirme efektleri ile yığın tahsis edilmesi gerekir (ya çağrı yapamazsınız
TLW

1
@TLW Yapıcılara gövde ekledikten sonra yığın tahsisi yoktur, godbolt.org/z/vkCD65
R2RT

8

Yapıcı gövdesi boşsa, atlanabilir veya varsayılan olarak ayarlanabilir:

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

Ardından varsayılan başlatma event_counts counts; bırakacağım counts.counts(varsayılan başlatma burada no-op) başlatılmamış ve değer başlatma event_counts counts{}; ilklendir değeri olacaktır counts.countsetkili bir sıfır ile doldurulması.


3
Ama sonra tekrar değer başlatmayı kullanmayı hatırlamanız gerekir ve OP varsayılan olarak güvenli olmasını ister.
doc

@doc, katılıyorum. Bu OP'nin istediği kesin çözüm değil. Ancak bu başlatma yerleşik türleri taklit eder. Çünkü int i;sıfır sıfırlanmadığını kabul ediyoruz. Belki de event_counts counts;sıfır başlatılmamış olduğunu kabul etmeli ve event_counts counts{};yeni varsayılanımızı yapmalıyız .
Evg

6

Çözümünü beğendim. İç içe yapı ve statik değişken de düşünmüş olabilirsiniz. Örneğin:

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

};

Statik değişken ile başlatılmamış yapıcı çağrısı daha uygun görünebilir:

event_counts e(event_counts::uninit);

Elbette yazmayı kaydetmek ve daha sistematik bir özellik haline getirmek için bir makro tanıtabilirsiniz

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

struct event_counts {
    UNINIT_TAG
}

struct other_counts {
    UNINIT_TAG
}

3

Bence bir enum bir etiket sınıfı ya da bir bool daha iyi bir seçimdir. Bir yapının örneğini iletmeniz gerekmez ve arayandan hangi seçeneği aldığınız açıktır.

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

Sonra örnekler oluşturmak şöyle görünür:

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

Veya etiketi sınıf yaklaşımı gibi yapmak için, etiket sınıfı yerine tek değerli bir enum kullanın:

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

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

Sonra bir örnek oluşturmanın sadece iki yolu vardır:

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

Size katılıyorum: enum daha basit. Ama belki bu satırı unuttun:event_counts() : counts{} {}
mavimsi

@bluish, niyetim countskoşulsuz olarak başlatmak değil , sadece INITayarlandığında.
TimK

@bluish Bir etiket sınıfı seçmenin temel sebebinin basitlik elde etmek değil, başlatılmamış nesnenin özel olduğunu işaret etmek olduğunu düşünüyorum, yani sınıf arayüzünün normal parçası yerine optimizasyon özelliğini kullanıyor. Hem boolve enumiyi, ama biz aşırı yerine parametresini kullanarak biraz farklı semantik gölge vardır farkında olmak zorunda. Birincisinde bir nesneyi açıkça parametrelendiriyorsunuz, dolayısıyla başlatılan / başlatılmamış duruş durumu haline gelirken, bir etiket nesnesini ctor'a aktarmak daha çok sınıfın bir dönüşüm gerçekleştirmesini istemek gibidir. Yani IMO sözdizimsel bir seçim meselesi değil.
doc

@TimK Ama OP dizinin başlatılması için varsayılan davranış istiyor, bu yüzden soruya çözümünüzün içermesi gerektiğini düşünüyorum event_counts() : counts{} {}.
mavimsi

@bluish Orijinal önerimde , talep edilmedikçe countsilklendirilir . Varsayılan kurucuyu önerdiğiniz gibi eklemek, varsayılan başlatmayı yapmanın iki farklı yolunu yapar, bu harika bir fikir değildir. Kullanmaktan kaçınan başka bir yaklaşım ekledim . std::fillNO_INITstd::fill
TimK

1

Sınıfınız için iki aşamalı bir başlatmayı düşünebilirsiniz :

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() = default;

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

Yukarıdaki yapıcı diziyi sıfıra başlatmaz. Dizi öğelerini sıfıra ayarlamak için, set_zero()inşaattan sonra üye işlevini çağırmanız gerekir .


7
Teşekkürler, bu yaklaşımı düşündüm ama varsayılanı güvende tutan bir şey istiyorum - varsayılan olarak sıfır ve yalnızca birkaç seçili yerde davranışları güvensiz olana geçersiz kıldım.
BeeOnRope

3
Bu ekstra bakım tüm alınacak gerektirecektir ama beklenen kullanımları başlatılmamış olması. Bu yüzden OPs çözümüne göre ekstra bir hata kaynağıdır.
ceviz

@BeeOnRope , varsayılan bağımsız değişkene std::functionbenzer bir şeyle yapıcı bağımsız değişken de sağlayabilir set_zero. Başlatılmamış bir dizi istiyorsanız, bir lambda işlevini iletirsiniz.
doc

1

Bunu şöyle yaparım:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

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

Derleyici, kullandığınızda tüm kodu atlayacak kadar akıllı olacak event_counts(false)ve sınıfınızın arayüzünü çok garip hale getirmek yerine tam olarak ne demek istediğinizi söyleyeceksiniz.


8
Verimlilik konusunda haklısınız, ancak boole parametreleri okunabilir istemci kodu için uygun değil. Birlikte okurken ve beyanı gördüğünüzde event_counts(false), bu ne anlama geliyor? Geri dönüp parametrenin adına bakmadan hiçbir fikriniz yok . En azından soruda gösterildiği gibi bir numaralandırma veya bu durumda bir sentinel / tag sınıfı kullanmak daha iyidir. Sonra, daha çok benzer bir beyan alırsınız event_counts(no_init), bu da anlamında herkes için açıktır.
Cody Gray

Bunun da iyi bir çözüm olduğunu düşünüyorum. Varsayılan ctor'u atabilir ve varsayılan değeri kullanabilirsiniz event_counts(bool initCountr = true).
doktor

Ayrıca, ctor açık olmalıdır.
doc

ne yazık ki şu anda C ++ adlandırılmış parametreleri desteklemiyor, ancak okunabilirliği kullanabilir boost::parameterve çağırabilirizevent_counts(initCounts = false)
phuclv

1
Funnily yeterince, @doc, event_counts(bool initCounts = true)aslında her parametre varsayılan bir değere sahip olduğundan , varsayılan bir kurucu. Gereksinim, bağımsız değişken belirtmeden çağrılabilir event_counts ec;olması, parametresiz olup olmaması veya varsayılan değerleri kullanması önemli değildir.
Justin Time - Monica'yı

1

Sadece yazarak kaydetmek için bir alt sınıf kullanırdım:

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

Kukla sınıftan, artık başlatılmayan kurucunun boolya intda bir şeyin argümanını değiştirerek, artık anımsatıcı olması gerekmediğinden kurtulabilirsiniz .

Ayrıca mirasın yerini değiştirebilir ve events_count_no_initcevaplarında önerilen Evg gibi varsayılan bir kurucu ile tanımlayabilir ve daha sonra events_countalt sınıf olabilirsiniz:

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

Bu ilginç bir fikir, ama aynı zamanda yeni bir tür tanıtmak sürtünmeye neden olacak gibi hissediyorum. Örneğin, aslında başlatılmamış bir event_countsşey istediğimde, tür olmasını isteyeceğim event_count, değil event_count_uninitialized, bu yüzden yazımdaki event_counts c = event_counts_no_init{};tasarrufların çoğunu ortadan kaldırdığımı düşünüyorum.
BeeOnRope

@BeeOnRope Pek çok amaç için bir event_count_uninitializednesne bir event_countnesnedir. Bütün kalıtım noktası bu, tamamen farklı türler değil.
Ross Ridge

Kabul etti, ama ovmak "çoğu amaç için" ile. Eğer atama görmeye çalışırsanız, örneğin - Onlar birbirinin yerine geçemez ecuiçin ecçalıştığını, ancak tersi. Veya şablon işlevlerini kullanırsanız, bunlar farklı türlerdir ve davranış aynı olsa bile farklı örneklerle biter (ve bazen statik şablon üyeleriyle olmayabilir). Özellikle bunun ağır kullanımı ile autokesinlikle kırpılabilir ve kafa karıştırıcı olabilir: Bir nesnenin başlatıldığı yolun türüne kalıcı olarak yansıtılmasını istemem.
BeeOnRope
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.