Bir kerelik 'if' yazmanın en zarif yolu


137

C ++ 17 beri iftam olarak böyle bir kez yürütülecek bir blok yazabilirsiniz :

#include <iostream>
int main() {
    for (unsigned i = 0; i < 10; ++i) {

        if (static bool do_once = true; do_once) { // Enter only once
            std::cout << "hello one-shot" << std::endl;
            // Possibly much more code
            do_once = false;
        }

    }
}

Bunu düşünüyor olabileceğimi biliyorum, ve bunu çözmenin başka yolları da var, ama yine de - bunu böyle bir şekilde yazmak mümkün do_once = falsemü , bu yüzden sonunda buna gerek yok mu?

if (DO_ONCE) {
    // Do stuff
}

Yardımcı bir işlev düşünüyorum do_once(), içeren static bool do_once, ama ya aynı işlevi farklı yerlerde kullanmak istersem? Bu bir zaman ve yer olabilir #definemi? Umarım değildir.


52
Neden sadece if (i == 0)? Yeterince açık.
SilvanoCerza

26
@SilvanoCerza Çünkü mesele bu değil. Bu if-bloğu, düzenli bir döngüde değil, birden çok kez yürütülen bazı işlevlerde bir yerde olabilir
nada

8
belki std::call_oncebir seçenektir (diş açma için kullanılır, ancak yine de işidir).
fdan

25
Örneğiniz, bize göstermediğiniz gerçek dünya sorununuzun kötü bir yansıması olabilir, ancak neden yalnızca bir kez arama işlevini döngüden kaldırmıyorsunuz?
rubenvb

14
Bir ifkoşulda başlatılan değişkenlerin olabileceği aklıma gelmedi static. Zekice.
HolyBlackCat

Yanıtlar:


144

Kullanım std::exchange:

if (static bool do_once = true; std::exchange(do_once, false))

Gerçeğin değerini tersine çevirmeyi daha kısa yapabilirsiniz:

if (static bool do_once; !std::exchange(do_once, true))

Ancak bunu çok kullanıyorsanız, süslü olmayın ve bunun yerine bir ambalaj oluşturmayın:

struct Once {
    bool b = true;
    explicit operator bool() { return std::exchange(b, false); }
};

Ve şöyle kullanın:

if (static Once once; once)

Değişkenin koşul dışında referans gösterilmemesi gerekir, bu nedenle ad bizi fazla satın almaz. Tanımlayıcıya özel bir anlam veren Python gibi diğer dillerden ilham alarak _şunları yazabiliriz:

if (static Once _; _)

Diğer iyileştirmeler: BSS bölümünden (@Deduplicator) yararlanın, zaten çalıştırdığımızda bellek yazma işleminden kaçının (@ShadowRanger) ve birçok kez test edecekseniz bir dal tahmin ipucu verin (örneğin, sorudaki gibi):

// GCC, Clang, icc only; use [[likely]] in C++20 instead
#define likely(x) __builtin_expect(!!(x), 1)

struct Once {
    bool b = false;
    explicit operator bool()
    {
        if (likely(b))
            return false;

        b = true;
        return true;
    }
};

32
Makrolar C + + çok nefret biliyorum ama bu sadece çok temiz görünüyor: #define ONLY_ONCE if (static bool DO_ONCE_ = true; std::exchange(DO_ONCE_, false))Olarak kullanılmak üzere:ONLY_ONCE { foo(); }
Fibbles

5
yani, üç kez "bir kez" yazarsanız, eğer ifadeler buna değer ise üç defadan fazla kullanın imo
Alan

13
Adı _, çevrilebilir dizeleri işaretlemek için birçok yazılımda kullanılır. İlginç şeylerin olmasını bekleyin.
Simon Richter

1
Seçiminiz varsa, statik durumun başlangıç ​​değerinin all-bits-zero olmasını tercih edin. Yürütülebilir biçimlerin çoğu sıfır alanın uzunluğunu içerir.
Deduplicator

7
_Değişken için kullanıldığında Pythonic kullanılamaz. Daha _sonra başvurulacak olan değişkenler için değil , yalnızca bir değişken sağlamanız gereken değerleri depolamak için kullanmazsınız, ancak değere ihtiyacınız yoktur. Genellikle bazı değerlere ihtiyacınız olduğunda ambalajı açmak için kullanılır. (Başka kullanım durumları vardır, ancak bunlar atılabilir değer durumundan oldukça farklıdır.)
jpmc26

91

Belki de en zarif çözüm değil ve gerçek bir şey görmüyorsunuz if, ancak standart kütüphane aslında bu durumu kapsıyor :, bkz std::call_once.

#include <mutex>

std::once_flag flag;

for (int i = 0; i < 10; ++i)
    std::call_once(flag, [](){ std::puts("once\n"); });

Buradaki avantaj, bunun iş parçacığı için güvenli olmasıdır.


3
Ben bu bağlamda std :: call_once farkında değildi. Ama bu çözüm ile std :: call_once kullandığınız her yer için bir std :: once_flag bildirmeniz gerekiyor, değil mi?
nada

11
Bu işe yarıyor, ancak basit bir çözüm için tasarlanmamıştı, fikir çok iş parçacıklı uygulamalar için. Bu basit bir şey için aşırıya kaçıyor çünkü dahili senkronizasyonu kullanıyor - hepsi bir düz tarafından çözülecek bir şey için. İplik güvenli bir çözüm istemiyordu.
Michael Chourdakis

9
@MichaelChourdakis Sana katılıyorum, bu aşırıya kaçmış. Bununla birlikte, daha az okunabilir if-hileli bir şeyin arkasına bir şey gizlemek yerine, ne yaptığınızı ifade etme ("bu kodu bir kez çalıştır") hakkında bilmek ve özellikle bilmek önemlidir.
lubgr

17
call_onceBeni görmek , bir kez bir şey aramak istediğin anlamına gelir. Deli, biliyorum.
Barry

3
@SergeyA çünkü dahili senkronizasyon kullanıyor - hepsi bir düz tarafından çözülecek bir şey için. İstenenden başka bir şey yapmanın oldukça deyimsel bir yolu.
Yörüngedeki Hafiflik Yarışları

52

C ++, zaten "( blok öncesi; koşul; blok sonrası )" dan oluşan yerleşik bir kontrol akış ilkeline sahiptir :

for (static bool b = true; b; b = false)

Veya hackier, ancak daha kısa:

for (static bool b; !b; b = !b)

Bununla birlikte, burada sunulan tekniklerin herhangi birinin (henüz?) Çok yaygın olmadıkları için dikkatle kullanılması gerektiğini düşünüyorum.


1
İlk seçeneği seviyorum (gerçi - burada birçok varyant gibi - iş parçacığı güvenli değil, bu yüzden dikkatli olun). İkinci seçenek ... okuyamayacağım (sert titreme ve sadece iki iplikle kez herhangi bir sayı yürütülecek edilebilir verir b == false: Thread 1değerlendirir !bve loop için girdiği Thread 2değerlendirir !bve döngü girer Thread 1ayarı döngü için malzeme yapar ve çıkar b == falseiçin !byani b = true... Thread 2döngü, ayarının şeyler yapar ve çıkar b == trueiçin !byani b = falseizin tüm süreci süresiz tekrarlanması)
CharonX

3
Bazı kodların sadece bir kez yürütülmesi gereken bir soruna en zarif çözümlerden birinin bir döngü olduğunu ironik buluyorum . +1
nada

2
Önlemek istiyorum b=!b, bu güzel görünüyor, ama aslında değerin yanlış olmasını istiyorsunuz, bu yüzden b=falsetercih edilecektir.
'

2
Yerel olarak çıkmadığı takdirde korunan bloğun tekrar çalışacağını unutmayın. Bu bile arzu edilebilir, ancak diğer tüm yaklaşımlardan farklıdır.
Davis Herring

29

C ++ 17'de yazabilirsiniz

if (static int i; i == 0 && (i = 1)){

idöngü gövdesi ile oynamaktan kaçınmak için. i(standart tarafından garanti) 0 ile başlar ve ardından ekspresyon ;setleri iiçin 1de değerlendirilir ilk kez.

C ++ 11'de lambda işleviyle aynı şeyi başarabileceğinizi unutmayın

if ([]{static int i; return i == 0 && (i = 1);}()){

bu da iilmek gövdesine sızdırılmaması açısından hafif bir avantaj taşır .


4
Üzgünüm - bu CALL_ONCE adında bir #define konursa daha okunabilir olurdu
nada

9
Başlatılması garanti edilen static int i;vakalardan biri (gerçekten emin değilim) olsa da , burada kullanmak çok daha açık . i0static int i = 0;
Kyle Willmon

7
Ne olursa olsun bir initialiser anlama için iyi bir fikir olduğunu kabul ediyorum
Orbit'te Hafiflik Yarışları

5
@Bathsheba Kyle yapmadı, bu nedenle iddianızın zaten yanlış olduğu kanıtlandı. Açık kod uğruna iki karakter eklemenin maliyeti nedir? Hadi, sen bir "baş yazılım mimarı "sın; Bunu bilmelisiniz :)
Yörüngedeki Hafiflik Yarışları

5
Eğer bir değişken için başlangıç değeri ortaya yazım düşünüyorsanız sana yardımcı olunabilir sanmıyorum, berrak bir aykırıdır ya da bu "bir şey korkak oluyor" öneriyor;)
Orbit Açıklık Yarışları

14
static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();

Bu çözüm ipliğe karşı güvenlidir (diğer önerilerin çoğunun aksine).


3
()Lambda beyanında isteğe bağlı (boşsa) biliyor muydunuz ?
Nonyme

9

Koşullu yerine somutlaştırdığınız statik nesnenin yapıcısına tek seferlik eylemi sarabilirsiniz.

Misal:

#include <iostream>
#include <functional>

struct do_once {
    do_once(std::function<void(void)> fun) {
        fun();
    }
};

int main()
{
    for (int i = 0; i < 3; ++i) {
        static do_once action([](){ std::cout << "once\n"; });
        std::cout << "Hello World\n";
    }
}

Ya da gerçekten böyle bir şeye benzeyen bir makroya bağlı kalabilirsiniz:

#include <iostream>

#define DO_ONCE(exp) \
do { \
  static bool used_before = false; \
  if (used_before) break; \
  used_before = true; \
  { exp; } \
} while(0)  

int main()
{
    for (int i = 0; i < 3; ++i) {
        DO_ONCE(std::cout << "once\n");
        std::cout << "Hello World\n";
    }
}

8

@Damon'un dediği gibi, std::exchangeazalan bir tam sayı kullanarak kullanmaktan kaçınabilirsiniz , ancak negatif değerlerin true değerine çözümlendiğini hatırlamanız gerekir. Bunu kullanmanın yolu:

if (static int n_times = 3; n_times && n_times--)
{
    std::cout << "Hello world x3" << std::endl;
} 

Bunu @ Acorn'un süslü ambalajına çevirmek şöyle görünür:

struct n_times {
    int n;
    n_times(int number) {
        n = number;
    };
    explicit operator bool() {
        return n && n--;
    };
};

...

if(static n_times _(2); _)
{
    std::cout << "Hello world twice" << std::endl;
}

7

std::exchange@Acorn tarafından önerildiği gibi kullanmak muhtemelen en deyimsel yol olsa da, bir değişim işlemi mutlaka ucuz değildir. Elbette statik başlatmanın iş parçacığı açısından güvenli olduğu garanti edilmesine rağmen (derleyicinize bunu yapmamasını söylemediğiniz sürece), bu nedenle performansla ilgili tüm noktalar staticanahtar kelime varlığında yine de biraz nafile olur .

Eğer (genellikle C ++ kullanarak insanlar gibi) mikro optimizasyonu hakkında endişeleriniz varsa, siz de çizebilecek boolve kullanımı intBunun yerine post-eksiltme (veya kullanma izni verecek olan yerine artışı aksine olarak, boolbir azaltma intolacaktır değil sıfıra doyurmak ...):

if(static int do_once = 0; !do_once++)

Eskiden boolarttırma / azaltma operatörleri vardı, ancak uzun zaman önce kullanımdan kaldırıldı (C ++ 11? Emin değil misiniz?) Ve C ++ 17'de tamamen kaldırılacaklardı. Bununla birlikte, intadil bir cezayı azaltabilirsiniz ve elbette bir Boole koşulu olarak çalışacaktır.

Bonus: Uygulayabilirsiniz do_twiceya da do_thricebenzer şekilde ...


Bunu test ettim ve ilk kez dışında birkaç kez ateş ediyor.
nada

@nada: Aptal bana, haklısın ... düzeltti. Eskiden bir boolzamanlar çalışıyordu ve azalıyordu. Ancak artış iyi çalışıyor int. Çevrimiçi demoya bakın: coliru.stacked-crooked.com/a/ee83f677a9c0c85a
Damon

1
Bu hala do_onceetrafı saran ve sonunda 0'a (ve tekrar tekrar ...) vuracak birçok kez yürütülebilme sorunu var .
nada

Daha kesin olmak gerekirse: Bu artık her INT_MAX kere çalıştırılacaktır.
nada

Peki evet, ama döngü sayacı dışında bu durumda da etrafı saran, bu bir sorun değil. Çok az insan herhangi bir şeyin 2 milyar (veya işaretsiz ise 4 milyar) yinelemesini gerçekleştirir. Eğer yaparlarsa, hala 64 bitlik bir tam sayı kullanabilirler. Mevcut en hızlı bilgisayarı kullanarak, etrafı sarmadan önce ölürsünüz, böylece dava açılmazsınız.
Damon

4

@ Bathsheba'nın bunun için büyük cevabına dayanarak - daha da basitleştirdi.

İçinde C++ 17şunları yapabilirsiniz:

if (static int i; !i++) {
  cout << "Execute once";
}

(Önceki sürümlerde, int i blok dışında bildirir. Ayrıca C :) de çalışır).

Basit bir deyişle: varsayılan sıfır ( 0) değerini alan i'yi bildirirsiniz . Sıfır falseydir, bu nedenle ünlem işareti ( !) operatörünü reddetmek için kullanırız. Daha sonra <ID>++, ilk önce işlenen (atanan vb.) Ve daha sonra artan operatörün artış özelliğini dikkate alırız.

Bu nedenle, bu blokta, başlatılacak ve 0blok yürütüldüğünde sadece bir kez değere sahip olacağım ve sonra değer artacaktır. !Operatörü sadece onu reddetmek için kullanırız.


1
Bu daha önce gönderilmiş olsaydı, büyük olasılıkla şimdi kabul edilen cevap olurdu. Harika, teşekkürler!
nada
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.