Yeni kodda hemen hemen her yerde C ++ 17's [[nodiscard]] kullanmamasının nedeni nedir?


70

C ++ 17 [[nodiscard]], programlayıcıların, işlevini, geri döndürülen nesne bir arayan tarafından atılırsa, derleyicinin bir uyarı üretecek şekilde işaretlemesini sağlayan bir özellik sunar; Aynı özellik tüm sınıf tipine eklenebilir.

Özgün teklifte bu özelliğin motivasyonunu okudum ve C ++ 20'nin std::vector::empty, isimlerinin dönüş değeriyle ilgili kesin bir anlam ifade etmeyen, standart fonksiyonlara nitelik katacağını biliyorum .

Serin ve kullanışlı bir özellik. Aslında, neredeyse çok kullanışlı görünüyor . Hakkında okuduğum her yerde [[nodiscard]], insanlar bunu seçmiş olduğunuz birkaç işlev veya türe ekler gibi dinler ve gerisini unuturlar. Peki neden okunamaz bir değer, özellikle de yeni kod yazarken özel bir durum olsun? Atılan bir iade değeri tipik olarak bir hata veya en azından bir kaynak israfı değil midir?

Ve C ++ 'ın tasarım ilkelerinden biri, derleyicinin olabildiğince fazla hata alması gerektiği anlamına gelmiyor mu?

Öyleyse, neden [[nodiscard]]hemen hemen her voidişlev türü olmayan ve neredeyse her sınıf türüne kendi eski, eski kodunuzu eklemiyorsunuz ?

Bunu kendi kodumla yapmaya çalıştım ve gayet iyi çalışıyor, ancak Java gibi hissetmeye başladığı kadar çok ayrıntılı. Derleyicileri , niyetinizi belirlediğiniz diğer birkaç durum dışında , varsayılan olarak atılan geri dönüş değerleri hakkında uyarmak çok daha doğal görünecektir [*] .

Standart tekliflerde, blog girişlerinde, Yığın Taşması sorularında veya internette başka bir yerde bu olasılık hakkında hiç tartışma görmedim, bir şey eksik olmalı.

Neden böyle bir tamirci yeni C ++ kodunda anlam ifade etmiyor? Ayrıntı [[nodiscard]]hemen hemen her yerde kullanmamak için tek neden midir?


[*] Teoride, standart kütüphane uygulamalarında olduğu [[maydiscard]]gibi fonksiyonlara da geriye dönük olarak eklenebilecek bir nitelik gibi bir şey olabilir printf.


22
Eh dönüş değeri fonksiyonları bir sürü var olabilir makul atılmalıdır. operator =Örneğin. Ve std::map::insert.
kullanıcı253751

2
@ immibis: Doğru. Standart kütüphane bu tür işlevlerle doludur. Ama kendi kodumla, son derece nadir olma eğilimindedirler; Uygulama kodunun kütüphane koduyla mı ilgili olduğunu düşünüyorsunuz?
Christian Hackl

9
"... kendimi Java gibi hissetmeye başlaması çok güzel ..." - bunu C ++ ile ilgili bir soruda okumak beni biraz
seğirdi

3
@ChristianHackl Yorumlar "dil basma" için doğru yer olmayabilir ve elbette Java da diğer dillere göre daha ayrıntılıdır. Ama başlık dosyaları, atama operatörleri, kopya kurucular ve düzgün kullanımı const(daha doğrusu "düz eski veri nesnesi" veya) bir başka "basit" sınıfı kabartmak yapabilirsiniz ölçüde C ++.
Marco13

2
@ Marco13: Bir yorumda çok fazla noktaya değinemiyorum. Kısaca açıklayalım: Java, dosya sayısını azaltan ancak eşleşmeyi artıran başlık dosyalarına sahip değildir; OTOH sizi her üst seviye sınıfı kendi dosyasına ve bir sınıftaki her fonksiyona koymaya zorlar. C ++ 'da, atama işleçlerini neredeyse hiç uygulamaz ve yapıcıları kendiniz kopyalamazsınız; bu işlevler, yalnızca sınıf tanımınızda veri üyelerine ihtiyaç duyduğunuz std::vectorveya kütüphane gibi standart kütüphane sınıflarıyla daha ilgilidir std::unique_ptr. Her iki dilde de çalıştım; Java fahişe bir dildir, ancak daha ayrıntılıdır.
Christian Hackl

Yanıtlar:


73

Eski standartlarla uyumlu olması gerekmeyen yeni kodda, bu özelliği makul olan her yerde kullanın. Ancak C ++ [[nodiscard]]için kötü bir varsayılan yapar. Sen öneriyorsun:

Derleyicileri, niyetinizi belirlediğiniz diğer birkaç durum dışında, varsayılan olarak atılan iade değerleri hakkında uyarmak çok daha doğal görünecektir.

Bu aniden mevcut, doğru kodun birçok uyarı vermesine neden olur. Bu tür bir değişikliğin teknik olarak geriye dönük olarak uyumlu olduğu düşünülebilse de, mevcut herhangi bir kod hala başarılı bir şekilde derlendiğinden, pratikte büyük bir anlamsal değişim olacaktır.

Çok geniş bir kod tabanına sahip olan mevcut ve olgun bir dil için tasarım kararları, tamamen yeni bir dil için tasarım kararlarından mutlaka farklıdır. Bu yeni bir dil olsaydı, varsayılan olarak uyarmak mantıklı olurdu. Örneğin, Nim dili, gereksiz değerlerin açıkça atılmasını gerektirir - bu, her ifade ifadesini C ++ 'a bir cast ile kaydırmaya benzer (void)(...).

Bir [[nodiscard]]özellik iki durumda en kullanışlıdır:

  • Bir fonksiyonun belirli bir sonucu döndürmenin ötesinde bir etkisi yoksa, yani safsa. Sonuç kullanılmazsa, çağrı kesinlikle işe yaramaz. Öte yandan, sonuç atmak yanlış olmaz.

  • eğer dönüş değeri kontrol edilmeli ise, örneğin atmak yerine hata kodlarını döndüren C benzeri bir arayüz için. Bu birincil kullanım durumudur. Deyimsel C ++ için bu oldukça nadir olacak.

Bu iki durum, böyle bir uyarının yanıltıcı olacağı bir değer döndüren çok büyük bir saf olmayan işlev alanı bırakmaktadır. Örneğin:

  • .pop()Bir öğeyi kaldıran ve kaldırılan öğenin bir kopyasını döndüren bir yöntemle sıra veri türünü göz önünde bulundurun . Böyle bir yöntem genellikle uygundur. Ancak, öğeyi yalnızca kopya almadan almadan kaldırmak istediğimiz bazı durumlar vardır. Bu tamamen meşru ve bir uyarı yardımcı olmaz. Farklı bir tasarım (örneğin std::vector) bu sorumlulukları ortadan kaldırır, ancak başka olumsuzlukları da vardır.

    Bazı durumlarda, yine de bir kopya yapılması gerektiğine dikkat edin, bu nedenle RVO sayesinde kopyayı geri almak ücretsiz olacaktır.

  • Diğer işlemlerin gerçekleştirilebilmesi için her işlemin nesneyi döndürdüğü akıcı arabirimleri göz önünde bulundurun. C ++ 'da en yaygın örnek akış ekleme işlecidir <<. [[nodiscard]]Her <<aşırı yüklemeye bir özellik eklemek son derece hantal olacaktır .

Bu örnekler, idiomatik C ++ kodunun “varsayılan olarak nodiscard ile C ++ 17” dili altında uyarılmadan derlenmesinin oldukça sıkıcı olacağını göstermektedir.

Parlak C ++ 17 kodunuzun (bu özellikleri kullanabileceğiniz), daha eski C ++ standartlarını hedefleyen kitaplıklarla birlikte derlenebileceğini unutmayın. Bu geriye dönük uyumluluk, C / C ++ ekosistemi için çok önemlidir. Bu nedenle, nodiscard'ı varsayılan yapmak, tipik, deyimsel kullanım durumları için birçok uyarıyla sonuçlanır - kütüphanenin kaynak kodunda geniş kapsamlı değişiklikler yapmadan düzeltemeyeceğiniz uyarılar.

Muhtemelen, buradaki sorun anlambilim değişikliği değil, her bir C ++ standardının özelliklerinin bir derleme birimi kapsamı için geçerli olduğunu ve dosya başına bir kapsam için geçerli olmadığını. Gelecekte C ++ standardı başlık dosyalarından uzaklaştığında, böyle bir değişiklik daha gerçekçi olacaktır.


6
Yararlı görüşler içerdiği için bunu hak ettim. Yine de, soruyu gerçekten cevaplayıp yanıtlamadığından emin değilim. Kaba işlevler gibi popveya bunlar gibi imkansız işlevler operator<<, hayali [[maydiscard]]özelliklerim tarafından açıkça işaretlendiğinden , hiçbir uyarı yapılmayacaktır. Bu tür işlevlerin genellikle inanmak istediğimden daha yaygın olduğunu veya genel olarak kendi kod tabanlarımdan çok daha yaygın olduğunu mu söylüyorsunuz ? Mevcut kodla ilgili ilk paragrafınız kesinlikle doğrudur, ancak yine de başka birinin şu anda tüm yeni kodlarını çiğnemekte [[nodiscard]]olup olmadığını ve neden olmasın diye merak ediyorum .
Christian Hackl

23
@ChristianHackl: C ++ 'nın tarihinde değerlerin const olarak döndürülmesinin iyi bir uygulama olduğu düşünülen bir zaman vardı. Söyleyemezsin returns_an_int() = 0, neden returns_a_str() = ""işe yaramalı? C ++ 11 bunun korkunç bir fikir olduğunu gösterdi, çünkü şimdi tüm bu kod hareketleri yasaklıyor çünkü değerler const. Bence bu dersten uygun bir şekilde alıp götürmek, “arayanların sonucunuzla ne yaptığınız size bağlı değil”. Sonucu gözardı etmek, arayanların yapmak isteyebileceği tamamen geçerli bir şeydir ve yanlış olduğunu bilmiyorsanız (çok yüksek bir çubuk), bunu engellememelisiniz.
GManNickG

13
Bir nodiscard da empty()açıktır çünkü isim oldukça belirsizdir: is_empty()bir belirteç clear()mi yoksa bir mutasyon mu? Genelde böyle bir mutatorun bir şey döndürmesini beklemezsiniz, bu nedenle bir dönüş değeri ile ilgili bir uyarı programlayıcıyı bir soruna uyarabilir. Daha net bir isimle, bu nitelik gerekli olmayacaktı.
amon

13
@HristianHackl: Başkalarının söylediği gibi, saf fonksiyonların ne zaman kullanılması gerektiğine dair iyi bir örnek olduğunu düşünüyorum. Bir adım daha ileri gideceğim ve bir [[pure]]özellik olması gerektiğini ve bunun ima edilmesi gerektiğini söyleyeceğim [[nodiscard]]. Bu şekilde berraktır neden bu sonuç göz ardı edilmemesi gerekir. Fakat saf işlevler dışında, bu davranışı olması gereken başka bir işlev sınıfı düşünemiyorum. Ve çoğu işlev saf değildir, bu nedenle nodiscard'ın varsayılan olarak doğru seçim olduğunu düşünmüyorum.
GManNickG

5
@GManNickG: Hata kodlarını görmezden gelen kodda hata ayıklama girişiminde bulunup başarısız olduktan sonra , hata kodlarının çoğunun varsayılan olarak kontrol edilmesi gerektiğine kuvvetle inanıyorum.
Mooing Duck

9

Nedenler [[nodiscard]]hemen hemen her yerde olmazdı :

  1. (majör :) Bu getirecek yolu benim başlıklarında çok fazla gürültü.
  2. (büyük :) Diğer insanların kodları hakkında güçlü spekülatif varsayımlar yapmam gerektiğini düşünmüyorum. Sana verdiğim dönüş değerini silmek ister misin? Tamam, kendini nakavt et.
  3. (küçük :) C ++ 14 ile uyumsuzluk garantisi veriyorsunuz

Şimdi, varsayılan olarak ayarladıysanız, tüm kütüphane geliştiricilerini tüm kullanıcılarının geri dönüş değerlerini atmamaya zorlamaya zorlarsınız. Bu korkunç olurdu. Veya onları [[maydiscard]]sayısız derecede, aynı zamanda korkunç olan fonksiyonlar eklemeye zorlarsınız .


0

Örnek olarak: operator << aramaya göre ya kesinlikle ihtiyaç duyulan ya da kesinlikle işe yaramaz olan bir dönüş değeri vardır. (std :: cout << x << y, akışa döndürüldüğü için birinciye ihtiyaç duyulur, ikincisi hiç kullanılmaz). Şimdi herkesin hata kontrolü için geri dönüş değerini attığı printf ile karşılaştırın, ancak daha sonra operatör << hata kontrolü yapmaz, bu yüzden ilk etapta bu kadar faydalı olamazdı. Yani her iki durumda da nodiscard sadece zarar verebilir.


Bu, sorulmamış bir soruyu yanıtlar, yani " [[nodiscard]]Her yerde kullanılmamasının nedeni nedir ?".
Christian Hackl
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.