C ++ 14 otomatik dönüş tipi kesinti ne zaman kullanılmalıdır?


144

GCC 4.8.0 yayınlandığında, C ++ 14'ün bir parçası olan otomatik dönüş tipi kesintiyi destekleyen bir derleyicimiz var. İle -std=c++1y, bunu yapabilirim:

auto foo() { //deduced to be int
    return 5;
}

Sorum şu: Bu özelliği ne zaman kullanmalıyım? Ne zaman gereklidir ve ne zaman kodu temizler?

Senaryo 1

Aklıma gelen ilk senaryo mümkün olduğunda. Bu şekilde yazılabilecek her fonksiyon olmalıdır. Buradaki sorun, kodu her zaman daha okunabilir hale getirmeyebilmesidir.

Senaryo 2

Bir sonraki senaryo, daha karmaşık dönüş türlerinden kaçınmaktır. Çok hafif bir örnek olarak:

template<typename T, typename U>
auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would
    return t + u;
}

Dönüş türünün açıkça parametrelere bağlı olması bazı durumlarda daha açık olabileceğini tahmin etsem de, bunun gerçekten bir sorun olacağına inanmıyorum.

Senaryo 3

Sonra, fazlalığı önlemek için:

auto foo() {
    std::vector<std::map<std::pair<int, double>, int>> ret;
    //fill ret in with stuff
    return ret;
}

C ++ 11'de bazen sadece return {5, 6, 7};bir vektörün yerine geçebiliriz , ancak bu her zaman işe yaramaz ve hem işlev üstbilgisinde hem de işlev gövdesinde türü belirtmemiz gerekir. Bu tamamen gereksizdir ve otomatik dönüş tipi kesinti bizi bu fazlalıktan kurtarır.

Senaryo 4

Son olarak, çok basit fonksiyonların yerine kullanılabilir:

auto position() {
    return pos_;
}

auto area() {
    return length_ * width_;
}

Bununla birlikte, bazen, tam türü bilmek isteyen işleve bakabiliriz ve orada sağlanmazsa, kodda nerede pos_bildirildiği gibi başka bir noktaya gitmeliyiz .

Sonuç

Bu senaryolar ortaya konduğunda, hangisi aslında bu özelliğin kodu daha temiz hale getirmede yararlı olduğu bir durum olduğunu kanıtlıyor? Burada bahsetmeyi ihmal ettiğim senaryolar ne olacak? Beni daha sonra ısırmaması için bu özelliği kullanmadan önce ne gibi önlemler almalıyım? Bu özelliğin masaya onsuz getiremediği yeni bir şey var mı?

Birden fazla sorunun bunun yanıtlanacağı perspektifleri bulmada yardımcı olması gerektiğini unutmayın.


18
Harika soru! Hangi senaryoların kodu daha iyi hale getirdiğini sorurken, hangi senaryoların daha da kötüleşeceğini merak ediyorum .
Drew Dormann

2
@DrewDormann, Ben de merak ediyorum. Yeni özellikleri kullanmayı seviyorum, ancak ne zaman kullanılacağını ve ne zaman kullanılamayacağını bilmek çok önemli. Bunu anlamak için aldığımız yeni özelliklerin ortaya çıktığı bir süre var, bu yüzden şimdi yapalım, böylece resmen geldiğinde hazırız :)
chris

2
@NicolBolas, Belki de, ama şimdi bir derleyicinin gerçek bir sürümünde olması, insanların kişisel kodda kullanmaya başlaması için yeterli olacaktır (kesinlikle bu noktada projelerden uzak tutulmalıdır). Kendi kodumda mümkün olan en yeni özellikleri kullanmayı seven insanlardan biriyim ve teklifin komite ile ne kadar iyi gittiğini bilmesem de, bu yeni seçeneğe ilk dahil olduğunu söylüyor şey. Daha sonra bırakılması daha iyi olabilir ya da geleceğini bildiğimizde (ne kadar iyi çalışacağını bilmiyorum) canlandı.
chris

1
@NicolBolas, Yardımcı olursa, şimdi benimsendi: p
chris

1
Mevcut cevaplar ->decltype(t+u), otomatik kesinti ile değiştirmenin SFINAE'yi öldürdüğünden bahsetmiyor gibi görünüyor .
Marc Glisse

Yanıtlar:


62

C ++ 11 benzer sorular doğurur: lambdalarda dönüş tipi kesinti ne zaman kullanılır ve autodeğişkenler ne zaman kullanılır .

C ve C ++ 03'teki soruya verilen geleneksel cevap, "türleri açıkça ifade ettiğimiz ifade sınırları boyunca, genellikle örtük olduklarını, ancak bunları yayınlarla açık hale getirebileceğimiz" olmuştur. C ++ 11 ve C ++ 1y, tür çıkarma araçlarını tanıtır, böylece türü yeni yerlerde bırakabilirsiniz.

Üzgünüm, ama bunu genel kurallar yaparak çözmeyeceksin. Belirli bir koda bakmanız ve her yerde türleri belirtmek için okunabilirliğe yardımcı olup olmadığına kendiniz karar vermeniz gerekir: kodunuzun "bu şeyin türü X" demeniz daha iyi mi yoksa "Bu şeyin türü, kodun bu kısmını anlamakla alakasız: derleyicinin bilmesi gerekiyor ve muhtemelen bunu çözebiliriz, ancak burada söylememiz gerekmiyor" mu?

"Okunabilirlik" nesnel olarak tanımlanmadığından [*] ve ayrıca okuyucuya göre değiştiğinden, bir stil kılavuzundan tamamen tatmin edilemeyen bir kod parçasının yazarı / editörü olarak sorumluluğunuz vardır. Bir stil kılavuzunun normları belirttiği ölçüde bile, farklı insanlar farklı normları tercih edecek ve tanıdık olmayan bir şeyi "daha az okunabilir" bulma eğiliminde olacaklardır. Dolayısıyla, belirli bir önerilen stil kuralının okunabilirliği genellikle sadece yürürlükteki diğer stil kuralları bağlamında değerlendirilebilir.

Tüm senaryolarınız (birincisi bile) birinin kodlama stili için kullanım alanı bulur. Şahsen ben en ikincisi en zorlayıcı kullanım örneği olarak görüyorum, ancak buna rağmen belgelerinize bağlı olacağını tahmin ediyorum. Bir işlev şablonunun dönüş türünün belgelenmiş olduğunu görmek çok yararlı değildir auto, oysa bunu belgelenmiş olarak görmek decltype(t+u)güvenebileceğiniz bir yayınlanmış arayüz oluşturur.

[*] Bazen birileri nesnel ölçümler yapmaya çalışır. Herhangi bir kişinin istatistiksel olarak anlamlı ve genel olarak uygulanabilir herhangi bir sonuç bulduğu küçük ölçüde, yazarın "okunabilir" içgüdüsü lehine çalışan programcılar tarafından tamamen göz ardı edilir.


1
Lambdas bağlantıları ile iyi bir nokta (bu daha karmaşık fonksiyon gövdelerine izin verir). Ana şey, daha yeni bir özellik olması ve hala her kullanım durumunun artılarını ve eksilerini dengelemeye çalışıyorum. Bunu yapmak için, neden kendim için kullanacağımın arkasındaki nedenleri görmek yararlı olur, böylece ben, kendimi neden yaptığımı tercih ettiğimi keşfedebilirim. Bunu çok düşünüyor olabilirim, ama ben böyleyim.
chris

1
@chris: dediğim gibi, sanırım hepsi aynı şey. Kodunuzun "bu şeyin türü X" demesi daha mı iyi, yoksa kodunuzun "bu şeyin türü ilgisiz," derlemesi bilmeli ve büyük olasılıkla çözebiliriz ama söylememize gerek yok ". Biz yazarken 1.0 + 27Ubiz yazarken biz ikincisi iddia ediyoruz (double)1.0 + (double)27Ubiz eski iddia ediyoruz. Fonksiyonun sadeliği, çoğaltma derecesi, kaçınma buna decltypekatkıda bulunabilir, ancak hiçbiri güvenilir bir şekilde belirleyici olmayacaktır.
Steve Jessop

1
Kodunuzun "bu şeyin türü X" demesi daha mı iyi, yoksa kodunuzun "bu şeyin türü ilgisiz," derlemesi bilmeli ve büyük olasılıkla çözebiliriz ama söylememize gerek yok ". - Bu cümle tam olarak aradığım şeyin çizgisinde. Bu özelliği kullanma seçenekleriyle ve autogenel olarak karşılaştığımda bunu dikkate alacağım .
chris

1
IDE'lerin "okunabilirlik sorununu" hafifletebileceğini eklemek istiyorum. Visual Studio'yu ele alalım, örneğin: autoAnahtar kelimenin üzerine gelirseniz , gerçek dönüş türünü görüntüler.
andreee

1
@andreee: bu sınırlar içinde doğrudur. Bir türün birçok takma adı varsa, gerçek türü bilmek her zaman umduğunuz kadar yararlı değildir. Örneğin bir yineleyici türü olabilir int*, ancak gerçekte önemli olan şey, bir şey varsa, bunun nedeni şu andaki oluşturma seçeneklerinizle olan int*şeydir std::vector<int>::iterator_type!
Steve Jessop

30

Genel olarak, işlev dönüş tipi bir işlevi belgelemek için çok yardımcıdır. Kullanıcı ne beklendiğini bilecektir. Ancak, fazlalıktan kaçınmak için bu geri dönüş tipini düşürmenin güzel olabileceğini düşündüğüm bir durum var. İşte bir örnek:

template<typename F, typename Tuple, int... I>
  auto
  apply_(F&& f, Tuple&& args, int_seq<I...>) ->
  decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...))
  {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
  }

template<typename F, typename Tuple,
         typename Indices = make_int_seq<std::tuple_size<Tuple>::value>>
  auto
  apply(F&& f, Tuple&& args) ->
  decltype(apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices()))
  {
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
  }

Bu örnek resmi komite belgesi N3493'ten alınmıştır . İşlevin amacı, applya öğelerini std::tuplebir işleve iletmek ve sonucu döndürmektir. int_seqVe make_int_sequygulamanın sadece parçasıdır ve muhtemelen sadece ne yaptığını anlamaya çalışan herhangi bir kullanıcıyı karıştıracaktır.

Gördüğünüz gibi, dönüş türü decltypedöndürülen ifadenin birinden başka bir şey değildir . Dahası, apply_kullanıcılar tarafından görülmesi gerekmediği için, aşağı yukarı türünü bir veya daha az aynı olduğunda belgelendirmenin yararlılığından emin değilim apply. Bu durumda, dönüş türünün düşürülmesinin işlevi daha okunabilir hale getirdiğini düşünüyorum. Bu tam dönüş türünün aslında N3915 standardına decltype(auto)ekleme teklifinde bırakıldığını ve değiştirildiğini unutmayın (ayrıca orijinal cevabımın bu makaleden önce geldiğine dikkat edin):apply

template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
    return forward<F>(f)(get<I>(forward<Tuple>(t))...);
}

template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
    using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>;
    return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{});
}

Bununla birlikte, çoğu zaman, bu dönüş tipini korumak daha iyidir. Yukarıda tarif ettiğim özel durumda, dönüş türü okunamaz ve potansiyel bir kullanıcı bunu bilmekten bir şey kazanamaz. Örneklerle iyi bir dokümantasyon çok daha faydalı olacaktır.


Henüz bahsedilmeyen başka bir şey: SFINAE ifadesinindeclype(t+u) kullanılmasına izin verirken , decltype(auto)( bu davranışı değiştirmek için bir teklif olmasına rağmen) yapmaz . Örneğin, foobarbir türün fooüye işlevini varsa çağıracak bir işlevi ele alalım veya varsa türün barüye işlevini çağırabilir ve bir sınıfın her zaman tam olarak fooveya her barikisinin birden aynı anda olduğunu varsayalım :

struct X
{
    void foo() const { std::cout << "foo\n"; }
};

struct Y
{
    void bar() const { std::cout << "bar\n"; }
};

template<typename C> 
auto foobar(const C& c) -> decltype(c.foo())
{
    return c.foo();
}

template<typename C> 
auto foobar(const C& c) -> decltype(c.bar())
{
    return c.bar();
}

Arama foobarörneğinde Xgösterecektir fooarama sırasında foobarbir örneği üzerinde Yirade ekran bar. Bunun yerine otomatik döndürme türü kesinti kullanırsanız ( decltype(auto)olsun veya olmasın ) SFINAE ifadesini almazsınız ve bunlardan foobarbirinin örneğini çağırırsınız Xveya Yderleme zamanı hatasını tetikler.


8

Asla gerekli değildir. Ne zaman yapmanız gerektiğine gelince, bununla ilgili çok farklı cevaplar alacaksınız. Aslında standardın kabul edilen bir parçası olana ve büyük derleyicilerin çoğunluğu tarafından aynı şekilde desteklenene kadar hiç söyleyemem.

Bunun ötesinde dini bir tartışma olacak. Şahsen söyleyebilirim ki - gerçek dönüş tipine asla koyma kodu daha net hale getirir, bakım için çok daha kolaydır (bir fonksiyonun imzasına bakabilir ve aslında kodu okumak zorunda kaldığında ne döndüğünü bilirim) ve bu olasılığı ortadan kaldırır bir tür döndürmesi gerektiğini ve derleyicinin başka bir sorun yarattığını düşünüyor (şimdiye kadar kullandığım her komut dosyası dilinde olduğu gibi). Bence otomobil dev bir hataydı ve büyüklük emirlerinin yardımdan çok acıya neden olacağını düşünüyorum. Diğerleri, programlama felsefelerine uyduğu için her zaman kullanmanız gerektiğini söyleyecektir. Her halükarda, bu site için kapsam dışıdır.


1
Farklı geçmişlere sahip insanların bu konuda farklı görüşleri olacağını kabul ediyorum, ancak bunun bir C ++ - ish sonucuna ulaşmasını umuyorum. (Neredeyse) her durumda, #defineC ++ 'ı VB'ye dönüştürmek için s kullanmak gibi dil özelliklerini başka bir dil haline getirmek için kötüye kullanmak mümkün değildir . Özellikler genellikle, dilin zihniyetinde, neyin programcılarının alışkın olduğuna göre, neyin uygun kullanım ve neyin olmadığı konusunda iyi bir fikir birliğine sahiptir. Aynı özellik birden fazla dilde mevcut olabilir, ancak her birinin kullanımıyla ilgili kendi yönergeleri vardır.
chris

2
Bazı özellikler iyi bir fikir birliğine sahiptir. Birçoğu bilmiyor. Boost'un çoğunluğunun kullanılmaması gereken çöp olduğunu düşünen birçok programcı biliyorum. Ben de onun C ++ için en büyük şey olduğunu düşünen bazı biliyorum. Her iki durumda da, üzerinde yapılacak bazı ilginç tartışmalar olduğunu düşünüyorum, ancak bu sitedeki yapıcı olmayan yakın seçeneğin gerçek bir örneği.
Gabe Sechan

2
Hayır, sana tamamen katılmıyorum ve bence autosaf bir nimet. Fazlalığı ortadan kaldırır. Bazen dönüş tipini tekrarlamak sadece bir acıdır. Bir lambda'yı iade etmek isterseniz, sonucu bir std::functiontepeye saklamak zorunda kalmadan imkansız olabilir, bu da biraz ek yüke neden olabilir.
Yongwei Wu

1
Tamamen gerekli olmaktan başka en az bir durum var. İşlevleri çağırmanız, ardından sonuçları döndürmeden önce günlüğe kaydetmeniz gerektiğini ve işlevlerin hepsinin aynı dönüş türüne sahip olması gerekmediğini varsayalım. Bunu, işlevleri ve bunların bağımsız değişkenlerini parametre olarak alan bir günlük işlevi aracılığıyla yaparsanız, günlük işlevinin dönüş türünü autoher zaman iletilen işlevin dönüş türüyle eşleşecek şekilde bildirmeniz gerekir .
Justin Time - Monica'yı

1
[Teknik olarak bunu yapmanın başka bir yolu daha vardır, ancak temel olarak aynı şeyi yapmak için şablon büyüsü kullanır ve yine de autogeri dönüş türü için günlükleme işlevine ihtiyaç duyar .]
Justin Time - Monica

7

Fonksiyonun basitliği ile bir ilgisi yok (bu sorunun şimdi silinmiş bir kopyası olarak).

Dönüş türü sabittir (kullanmayın auto) veya şablon parametresine karmaşık bir şekilde bağımlıdır ( autoçoğu durumda, decltypebirden çok dönüş noktası olduğunda eşleştirilmiş olarak kullanın ).


3

Gerçek bir üretim ortamını düşünün: birçok fonksiyon ve birim testi, dönüş türüne bağımlıdır foo(). Şimdi, dönüş türünün herhangi bir nedenle değişmesi gerektiğini varsayalım.

Dönüş türü autoher yerdeyse foo()ve autodöndürülen değeri alırken arayanlar ve ilgili işlevler kullanılırsa , yapılması gereken değişiklikler asgari düzeydedir. Değilse, bu saatlerce son derece sıkıcı ve hataya açık çalışma anlamına gelebilir.

Gerçek dünyadaki bir örnek olarak, bir modülü her yerde ham işaretçiler kullanmaktan akıllı işaretleyicilere değiştirmem istendi. Birim testlerinin düzeltilmesi, gerçek koddan daha acı vericiydi.

Bunun ele alınabileceği başka yollar olsa da, autogeri dönüş türlerinin kullanımı iyi bir uyum gibi görünüyor.


1
Bu durumlarda yazmak daha iyi olmaz mıydı decltype(foo())?
Ören S

Şahsen, typedefs ve takma beyanların (yani usingtip beyanı) bu durumlarda, özellikle sınıf kapsamına alındıklarında daha iyi olduğunu hissediyorum .
MAChitgarha

3

Dönüş türü otomatik mükemmel nerede bir örnek vermek istiyorum:

Uzun bir sonraki işlev çağrısı için kısa bir takma ad oluşturmak istediğinizi düşünün. Otomatik ile orijinal dönüş türüne dikkat etmeniz gerekmez (belki gelecekte değişecektir) ve kullanıcı gerçek dönüş türünü almak için orijinal işlevi tıklatabilir:

inline auto CreateEntity() { return GetContext()->GetEntityManager()->CreateEntity(); }

Not: Bu soruya bağlı.


2

Senaryo 3 için, döndürülecek yerel değişkenle işlev imzasının dönüş türünü döndürürüm. İstemci programcılarının işlevin geri döndüğünü açıklığa kavuşturacaktır. Bunun gibi:

Senaryo 3 Yedekliliği önlemek için:

std::vector<std::map<std::pair<int, double>, int>> foo() {
    decltype(foo()) ret;
    return ret;
}

Evet, otomatik bir anahtar kelimesi yoktur, ancak temel, yedekliliği önlemek ve kaynağa erişimi olmayan programcılara daha kolay bir zaman vermek için aynıdır.


2
IMO bunun için daha iyi bir çözüm, bir olarak temsil edilmesi amaçlanan kavram için alana özel bir ad sağlamak vector<map<pair<int,double>,int>ve daha sonra bunu kullanmaktır.
davidbak
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.