Std :: Optional nasıl kullanılır?


135

Belgelerini okuyorum ve std::experimental::optionalne işe yaradığı hakkında iyi bir fikrim var, ancak ne zaman kullanmam gerektiğini veya nasıl kullanmam gerektiğini anlamıyorum. Site henüz herhangi bir örnek içermiyor, bu da benim için bu nesnenin gerçek kavramını kavramayı zorlaştırıyor. Ne zaman std::optionalkullanmak iyi bir seçimdir ve önceki Standartta (C ++ 11) bulunmayanları nasıl telafi eder?


19
Boost.optional dokümanlar bazı ışık tutabilir.
juanchopanza

Std :: unique_ptr gibi görünüyor genel olarak aynı kullanım durumlarına hizmet edebilir. Sanırım yeniye karşı bir şeyiniz varsa, isteğe bağlı olarak tercih edilebilir, ancak bana öyle geliyor ki (geliştiriciler | uygulamalar) yeniye bu tür bir bakış açısıyla küçük bir azınlıktadır ... AFAICT, isteğe bağlı harika bir fikir DEĞİL. En azından çoğumuz onsuz rahatça yaşayabilirdik. Şahsen, benzersiz_ptr ile isteğe bağlı arasında seçim yapmak zorunda olmadığım bir dünyada daha rahat olurdum. Bana deli deyin, ama Python'un Zen haklı: bir şeyi yapmanın tek bir doğru yolu olsun!
allyourcode

19
Yığın üzerinde silinmesi gereken bir şeyi her zaman ayırmak istemiyoruz, bu yüzden hiçbir unique_ptr isteğe bağlı yerine geçmez.
Krum

5
@allyourcode Hiçbir işaretçi onun yerine geçmez optional. Bir optional<int>veya hatta istediğinizi hayal edin <char>. Dinamik olarak ayırma, referans alma ve sonra silme işleminin gerçekten "Zen" olduğunu düşünüyor musunuz - aksi takdirde herhangi bir tahsis gerektirmeyen ve yığına ve hatta muhtemelen bir kayıt defterine sıkıca sığabilecek bir şey?
underscore_d

1
İçerdiği değerin yığın ile yığın tahsisi arasında şüphesiz ortaya çıkan diğer bir fark, şablon bağımsız değişkeninin (için olabilirken) tamamlanmamış bir tür std::optionalolamayacağıdır std::unique_ptr. Daha kesin olarak, standart T [...] Destructible'ın gereksinimlerini karşılamasını gerektirir .
dan_din_pantelimon

Yanıtlar:


173

Aklıma gelen en basit örnek:

std::optional<int> try_parse_int(std::string s)
{
    //try to parse an int from the given string,
    //and return "nothing" if you fail
}

Aynı şey bunun yerine bir referans argümanıyla da gerçekleştirilebilir (aşağıdaki imzada olduğu gibi), ancak kullanmak std::optionalimzayı ve kullanımı daha güzel kılar.

bool try_parse_int(std::string s, int& i);

Bunu yapmanın başka bir yolu da özellikle kötü :

int* try_parse_int(std::string s); //return nullptr if fail

Bu, dinamik bellek tahsisi, sahiplik endişesi vb. Gerektirir - her zaman yukarıdaki diğer iki imzadan birini tercih edin.


Başka bir örnek:

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};

Bu, std::unique_ptr<std::string>her telefon numarası için a gibi bir şeye sahip olmak yerine son derece tercih edilir ! std::optionalsize performans için harika olan veri konumunu sağlar.


Başka bir örnek:

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};

Aramada belirli bir anahtar yoksa, "değer yok" döndürebiliriz.

Bunu şu şekilde kullanabilirim:

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");

Başka bir örnek:

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);

Bu, diyelim ki, olası her kombinasyonu alan max_count(veya almayan) ve min_match_score(veya almayan) dört fonksiyon aşırı yüklemesine sahip olmaktan çok daha mantıklıdır !

Ayrıca ortadan kaldırır lanetli "Geçer -1için max_countveya" Geçer Bir sınır istemiyorsanız " std::numeric_limits<double>::min()için min_match_scorebir asgari puanı istemiyorsanız"!


Başka bir örnek:

std::optional<int> find_in_string(std::string s, std::string query);

Sorgu dizesi içinde değilse s, "hayır int" istiyorum - birinin bu amaçla kullanmaya karar verdiği özel bir değer değil (-1?).


Ek örnekler için boost::optional belgelere bakabilirsiniz . boost::optionalve std::optionaltemelde davranış ve kullanım açısından aynı olacaktır.


13
@gnzlbg std::optional<T>sadece bir Tve bir bool. Üye işlevi uygulamaları son derece basittir. Performansı kullanırken gerçekten bir sorun olmamalı - bir şeyin isteğe bağlı olduğu zamanlar vardır, bu durumda bu genellikle iş için doğru araçtır.
Timothy Shields

8
@TimothyShields std::optional<T>bundan çok daha karmaşık. Yerleşimi newve uygun hizalama ve boyuta sahip daha birçok şeyi, onu constexprdiğer şeylerin yanı sıra birebir tip (yani birlikte kullanma ) yapmak için kullanır . Saf Tve boolyaklaşım oldukça çabuk başarısız olur.
Rapptz

15
@Rapptz Hattı 256: union storage_t { unsigned char dummy_; T value_; ... }Satır 289: struct optional_base { bool init_; storage_t<T> storage_; ... }Nasıl yani değil "a Tve bool"? Uygulamanın çok zor ve önemsiz olduğuna tamamen katılıyorum, ancak kavramsal ve somut olarak tip a Tve a'dır bool. "Saf Tve boolyaklaşım oldukça çabuk başarısız olur." Koda bakarken bu ifadeyi nasıl yapabilirsiniz?
Timothy Shields

12
@Rapptz hala bir bool ve int için boşluk depoluyor. Sendika sadece, gerçekten istenmiyorsa, isteğe bağlı olanı bir T oluşturmama yapmak için vardır. Hâlâ struct{bool,maybe_t<T>}sendika, struct{bool,T}her durumda bir T oluşturacak olan yapmamak için orada .
PeterT

12
@allyourcode Çok güzel soru. Hem std::unique_ptr<T>ve std::optional<T>rolünü yerine getirmek Bir anlamda do "isteğe bağlı T". Aralarındaki farkı "uygulama ayrıntıları" olarak tanımlardım: ekstra ayırmalar, bellek yönetimi, veri konumu, taşıma maliyeti, vb. Asla sahip olamayacağım std::unique_ptr<int> try_parse_int(std::string s);, çünkü bunun için bir neden olmasa bile her çağrı için bir ayırmaya neden olur. . Asla bir sınıfım olmayacak std::unique_ptr<double> limit;- neden bir ayırma yapıp veri yerelliğini kaybedelim?
Timothy Shields

35

Yeni kabul edilen makaleden bir örnek alınmıştır : N3672, std :: optional :

 optional<int> str2int(string);    // converts int to string if possible

int get_int_from_user()
{
     string s;

     for (;;) {
         cin >> s;
         optional<int> o = str2int(s); // 'o' may or may not contain an int
         if (o) {                      // does optional contain a value?
            return *o;                  // use the value
         }
     }
}

13
Çünkü "hata" anlamına geldiği varsayılan "hayali" bir değerin etrafından dolaşmak yerine, yukarı veya aşağı çağrı hiyerarşisine sahip olup olmadığınız hakkındaki bilgileri iletebilirsinizint .
Luis Machuca

1
@Wiz Bu aslında harika bir örnek. (A) str2int()dönüşümü istediği gibi uygulamaya izin verir , (B) elde etme yöntemine bakılmaksızın string sve (C) optional<int>aptal bir büyü numarası, bool/ referans veya dinamik ayırma tabanlı yol yerine tam anlam taşır . Bunu yapıyor.
underscore_d

10

ama ne zaman kullanmam gerektiğini veya nasıl kullanmam gerektiğini anlamıyorum.

Bir API yazarken ve "dönüş olmaması" değerinin bir hata olmadığını ifade etmek istediğinizde düşünün. Örneğin, bir soketten veri okumanız gerekir ve bir veri bloğu tamamlandığında, onu ayrıştırıp geri döndürürsünüz:

class YourBlock { /* block header, format, whatever else */ };

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);

Eklenen veriler ayrıştırılabilir bir bloğu tamamladıysa, onu işleyebilirsiniz; aksi takdirde verileri okumaya ve eklemeye devam edin:

void your_client_code(some_socket_object& socket)
{
    char raw_data[1024]; // max 1024 bytes of raw data (for example)
    while(socket.read(raw_data, 1024))
    {
        if(auto block = cache_and_get_block(raw_data))
        {
            // process *block here
            // then return or break
        }
        // else [ no error; just keep reading and appending ]
    }
}

Düzenleme: geri kalan sorularınızla ilgili olarak:

Std :: isteğe bağlı ne zaman kullanmak için iyi bir seçimdir

  • Bir değeri hesapladığınızda ve onu döndürmeniz gerektiğinde, bir çıktı değerine (üretilemeyen) bir referans almak yerine değere göre dönmek daha iyi anlambilim sağlar.

  • Söz konusu müşteri kodunu sağlamak istediğinizde sahiptir hata kontrol olmayabilir istemci kodu yazıyor kim çıktı değerini (kontrol etmek - Eğer bir çekirdek dökümü almak un-başlatılmış işaretçi kullanmayı denerseniz, siz bir un- kullanmaya kalkarsanız başlatılmış std :: isteğe bağlı, yakalanabilir bir istisna elde edersiniz).

[...] ve önceki Standartta (C ++ 11) bulunmayanları nasıl telafi eder.

C ++ 11'den önce, "değer döndürmeyebilecek işlevler" için farklı bir arabirim kullanmanız gerekiyordu - ya işaretçi ile dönüp NULL olup olmadığını kontrol edin ya da bir çıktı parametresini kabul edin ve "not available" için bir hata / sonuç kodu döndür ".

Her ikisi de doğru yapmak için istemci uygulayıcıdan fazladan çaba ve ilgi gerektirir ve her ikisi de bir kafa karışıklığı kaynağıdır (ilki, istemci uygulayıcısını bir işlemi bir ayırma olarak düşünmeye zorlar ve istemci kodunu işaretçi işleme mantığını uygulamak için gerektirir ve ikincisi izin verir. geçersiz / başlatılmamış değerleri kullanmaktan kurtulmak için istemci kodu).

std::optional önceki çözümlerde ortaya çıkan sorunları güzelce halleder.


Temelde aynı olduklarını biliyorum ama boost::bunun yerine neden kullanıyorsunuz std::?
0x499602D2

4
Teşekkürler - düzelttim (kullandım boost::optionalçünkü yaklaşık iki yıl kullandıktan sonra ön korteksime kodlandı).
utnapistim

1
Bu bana kötü bir örnek olarak çarpıyor, çünkü birden fazla blok tamamlanırsa yalnızca biri iade edilebilir ve geri kalanı atılabilir. İşlev, bunun yerine muhtemelen boş bir blok koleksiyonu döndürmelidir.
Ben Voigt

Haklısın; kötü bir örnekti; "Varsa ilk bloğu ayrıştır" örneğimi değiştirdim.
utnapistim

4

Yapılandırma dosyalarından alınan isteğe bağlı verileri temsil etmek için genellikle isteğe bağlı verileri kullanırım, yani bu verilerin (bir XML belgesindeki beklenen, ancak gerekli olmayan bir öğeyle olduğu gibi) isteğe bağlı olarak sağlandığını, böylece açık ve net bir şekilde veriler aslında XML belgesinde mevcuttu. Özellikle veriler, "boş" ve "ayarlı" duruma (bulanık mantık) karşı "ayarlanmadı" durumuna sahip olduğunda. İsteğe bağlı, set ve not set açık olduğunda, boş da 0 veya null değeriyle açık olur.

Bu, "ayarlanmadı" değerinin "boş" ile nasıl eşdeğer olmadığını gösterebilir. Kavram olarak, bir int (int * p) işaretçisi bunu gösterebilir, burada bir null (p == 0) ayarlanmadı, 0 (* p == 0) değeri ayarlı ve boş ve diğer herhangi bir değer (* p <> 0) bir değere ayarlanır.

Pratik bir örnek olarak, geometrinin oluşturma bayraklarını geçersiz kılabildiği (set), oluşturma bayraklarını devre dışı bırakabildiği (0'a ayarlı) ya da basitçe yapmayabildiği, işleme bayrakları adı verilen bir değere sahip bir XML belgesinden alınmış bir geometri parçam var render bayraklarını etkilediğinde (ayarlanmadı), isteğe bağlı bir yöntem bunu göstermenin açık bir yolu olacaktır.

Açıkçası, bu örnekte bir int için bir işaretçi, hedefi gerçekleştirebilir veya daha iyisi, daha temiz bir uygulama sunabileceği için bir paylaşım işaretçisi yapabilir, ancak bu durumda bunun kod netliği ile ilgili olduğunu iddia ediyorum. Boş değer her zaman "ayarlanmamış" mıdır? Bir işaretçi ile, boş tam anlamıyla tahsis edilmemiş veya yaratılmamış anlamına geldiği için açık değildir, ancak yapabilse de , gerekli olmayabilir ortalama "ayarlanmadı". Bir işaretçinin serbest bırakılması gerektiğine ve iyi uygulamada 0'a ayarlanması gerektiğine dikkat çekmek önemlidir, ancak, paylaşılan bir işaretçide olduğu gibi, isteğe bağlı bir açık temizleme gerektirmez, bu nedenle temizlemeyi karıştırmanın bir endişesi yoktur. isteğe bağlı ayarlanmamıştır.

Bunun kod netliği ile ilgili olduğuna inanıyorum. Netlik, kod bakımı ve geliştirme maliyetlerini azaltır. Kod amacının net bir şekilde anlaşılması inanılmaz derecede değerlidir.

Bunu temsil etmek için bir göstericinin kullanılması, işaretçi kavramının aşırı yüklenmesini gerektirir. "Boş" ifadesini "ayarlanmadı" olarak temsil etmek için, genellikle bu amacı açıklamak için kod aracılığıyla bir veya daha fazla yorum görebilirsiniz. Bu, isteğe bağlı bir çözüm yerine kötü bir çözüm değil, ancak, yorumlar uygulanamaz (derleme gibi) olduğundan, her zaman açık yorumlar yerine örtük uygulamayı tercih ederim. Geliştirme için bu örtük öğelerin örnekleri (geliştirilmekte olan ve yalnızca amacı uygulamak için sağlanan makaleler), çeşitli C ++ stil yayınlarını, "const" (özellikle üye işlevlerinde) ve "bool" türünü içerir. Muhtemelen, herkes niyetlere veya yorumlara itaat ettiği sürece bu kod özelliklerine gerçekten ihtiyacınız yoktur.

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.