Yeni standart sürümlerle C ++ 'da hiç sessiz davranış değişiklikleri oldu mu?


104

(Listeyi değil, noktayı kanıtlamak için bir veya iki örnek arıyorum.)

C ++ standardındaki bir değişikliğin (örneğin 98'den 11'e, 11'den 14'e vb.) Mevcut, iyi biçimlendirilmiş, tanımlanmış davranışlı kullanıcı kodunun davranışını sessizce değiştirdiği hiç oldu mu? yani, daha yeni standart sürümle derlerken herhangi bir uyarı veya hata olmadan?

Notlar:

  • Uygulayıcı / derleyici yazar seçimlerini değil, standartların zorunlu tuttuğu davranışları soruyorum.
  • Kodu ne kadar az icat ederse, o kadar iyidir (bu sorunun cevabı olarak).
  • Gibi sürüm algılamalı kodu kastetmiyorum #if __cplusplus >= 201103L.
  • Bellek modeliyle ilgili cevaplar gayet iyi.

Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Samuel Liew

3
Bu sorunun neden kapandığını anlamıyorum. " Yeni standart sürümlerle C ++ 'da hiç sessiz davranış değişiklikleri oldu mu? " Mükemmel bir şekilde odaklanmış görünüyor ve sorunun gövdesi bundan uzaklaşmıyor gibi görünüyor.
Ted Lyngmo

Bence en büyük sessiz kırılma değişikliği, 'nin yeniden tanımlanmasıdır auto. C ++ 11'den önce, auto x = ...;bir int. Sonra ne olduğunu ilan eder ....
Raymond Chen

@RaymondChen: Bu değişiklik yalnızca int'leri örtük olarak tanımlıyorsanız, ancak açık bir şekilde was auto-type değişkenlerini söylediyseniz sessizdir . Bence bu tür bir kod yazacak dünyadaki insan sayısına muhtemelen güvenebilirsiniz, karıştırılmış C kodu yarışmaları dışında ...
einpoklum

Doğru, bu yüzden seçtiler. Ama anlambilimde büyük bir değişiklik oldu.
Raymond Chen

Yanıtlar:


113

Dönüş tipi string::datagelen değişikliklere const char*karşı char*kesinlikle bir fark yaratabilir 17'de C ++

void func(char* data)
{
    cout << data << " is not const\n";
}

void func(const char* data)
{
    cout << data << " is const\n";
}

int main()
{
    string s = "xyz";
    func(s.data());
}

Biraz yapmacık ama bu yasal program çıktısını C ++ 14'ten C ++ 17'ye değiştirecek.


7
Oh, std::stringC ++ 17'deki değişikliklerin farkında bile değildim . Bir şey olursa, C ++ 11 değişikliklerinin bir şekilde sessiz davranış değişikliğine neden olabileceğini düşünürdüm. +1.
einpoklum

9
Yapılmış olsun ya da olmasın, bu iyi biçimlendirilmiş kodda oldukça iyi bir değişiklik olduğunu gösterir.
David C. Rankin

Bir kenara, değişiklik, bir std :: string'in içeriğini yerinde değiştirdiğinizde, belki de char * üzerinde çalışan eski işlevler aracılığıyla komik ama meşru kullanım durumlarına dayanmaktadır . Bu artık tamamen meşru: bir vektörde olduğu gibi, işleyebileceğiniz temelde bitişik bir dizi olduğuna dair bir garanti var (geri dönen referanslar aracılığıyla her zaman yapabilirsiniz; şimdi daha doğal ve açık hale getirildi). Olası kullanım durumları düzenlenebilir, sabit uzunlukta veri kümeleridir (örneğin, bir std :: kapsayıcıya dayanıyorsa, yaşam süresi yönetimi, kopyalanabilirlik vb. Gibi STL hizmetlerini korur)
Peter - Monica'yı

81

Cevabı bu soruyu nasıl tek kullanan bir vektör başlatılıyor gösterileri size_typedeğeri C ++ 03 ve C ++ 11 arasında farklı davranışlara neden olabilir.

std::vector<Something> s(10);

C ++ 03 varsayılan olarak öğe türünün geçici bir nesnesini oluşturur Somethingve vektördeki her öğeyi o geçiciden kopyalayıp oluşturur.

C ++ 11, vektördeki her bir öğeyi varsayılan olarak oluşturur.

Birçok (çoğu?) Durumda, bunlar eşdeğer nihai durumla sonuçlanır, ancak buna gerek yoktur. SomethingVarsayılan / kopya yapıcılarının uygulanmasına bağlıdır .

Bu uydurma örneğe bakın :

class Something {
private:
    static int counter;

public:
    Something() : v(counter++) {
        std::cout << "default " << v << '\n';
    }

    Something(Something const & other) : v(counter++) {
        std::cout << "copy " << other.v << " to " << v << '\n';
    }

    ~Something() {
        std::cout << "dtor " << v << '\n';
    }

private:
    int v;
};

int Something::counter = 0;

C ++ 03 varsayılan-yapısı olacak bir Somethingile v == 0daha o birinden sonra kopyalayıp yapı on. Sonunda vektör, vdeğerleri 1'den 10'a kadar olan on nesne içerir .

C ++ 11, her öğeyi varsayılan olarak oluşturacaktır. Kopyalama yapılmaz. Sonunda vektör, vdeğerleri 0'dan 9'a kadar olan on nesne içerir .


@einpoklum Yine de uydurma bir örnek ekledim. :)
cdhowie

3
Yapılmış olduğunu sanmıyorum. Farklı kurucular genellikle bellek ayırma gibi şeyler üzerinde farklı davranırlar. Bir yan etkiyi başka bir yan etkiyle (G / Ç) değiştirdiniz.
einpoklum

17
@cdhowie Hiç yapmacık değil. Yakın zamanda bir UUID sınıfı üzerinde çalışıyordum. Varsayılan kurucu rastgele bir UUID oluşturdu. Bu olasılık hakkında hiçbir fikrim yoktu, sadece C ++ 11 davranışını varsaydım.
John

5
Bunun önemli olacağı yaygın olarak kullanılan gerçek dünya sınıflarından biri OpenCV'dir cv::mat. Varsayılan kurucu yeni bellek ayırırken, copy yapıcısı mevcut belleğe yeni bir görünüm oluşturur.
jpa

Buna yapmacık bir örnek demezdim, davranıştaki farklılığı açıkça gösteriyor.
David Waterworth

51

Standart Ek C'de [diff] son değişikliklerin bir listesine sahiptir . Bu değişikliklerin çoğu sessiz davranış değişikliğine yol açabilir.

Bir örnek:

int f(const char*); // #1
int f(bool);        // #2

int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2

7
@einpoklum En azından bir düzine kodun mevcut kodun "anlamını değiştirdiği" veya onları "farklı şekilde çalıştırdığı" söyleniyor.
cpplearner

4
Bu özel değişikliğin gerekçesini nasıl özetlersiniz?
Nayuki

4
@Nayuki, boolsürümü kullanmanın kendiliğinden amaçlanan bir değişiklik olmadığından emin , sadece diğer dönüştürme kurallarının bir yan etkisi. Gerçek niyet, karakter kodlamaları arasındaki karışıklığın bir kısmını durdurmak olacaktır, asıl değişiklik eskiden u8değişmezlerin verdiği const char*ama şimdi verdiği gerçek değişikliktir const char8_t*.
civarı

25

Standart kitaplığa her yeni yöntem (ve genellikle işlev) eklediklerinde bu olur.

Standart bir kitaplık türünüz olduğunu varsayalım:

struct example {
  void do_stuff() const;
};

oldukça basit. Bazı standart revizyonlarda, yeni bir yöntem veya aşırı yük veya herhangi bir şeyin yanına eklenir:

struct example {
  void do_stuff() const;
  void method(); // a new method
};

bu, mevcut C ++ programlarının davranışını sessizce değiştirebilir.

Bunun nedeni, C ++ 'nın şu anda sınırlı yansıtma yeteneklerinin, böyle bir yöntemin var olup olmadığını tespit etmek ve ona göre farklı kod çalıştırmak için yeterli olmasıdır.

template<class T, class=void>
struct detect_new_method : std::false_type {};

template<class T>
struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};

bu yeniyi tespit etmenin nispeten basit bir yoludur method, sayısız yol vardır.

void task( std::false_type ) {
  std::cout << "old code";
};
void task( std::true_type ) {
  std::cout << "new code";
};

int main() {
  task( detect_new_method<example>{} );
}

Aynı şey, yöntemleri sınıflardan kaldırdığınızda da olabilir.

Bu örnek, bir yöntemin varlığını doğrudan tespit ederken, dolaylı olarak gerçekleşen bu tür şeyler daha az tartışılabilir. Somut bir örnek olarak, bir şeyin yinelenebilir olup olmadığına göre bir konteyner olarak serileştirilip serileştirilemeyeceğine veya ham baytlara işaret eden bir veriye ve bir boyut üyesine sahip olup olmadığına karar veren bir serileştirme motorunuz olabilir. diğeri.

Standart gider ve .data()bir konteynere bir yöntem ekler ve aniden tür, serileştirme için kullandığı yolu değiştirir.

C ++ standardının yapabileceği tek şey, eğer donmak istemiyorsa, sessizce kırılan kod türünü nadir veya bir şekilde mantıksız hale getirmektir.


3
Soruyu SFINAE'yi hariç tutacak şekilde nitelendirmeliydim çünkü kastettiğim tam olarak bu değildi ... ama evet, bu doğru, yani +1.
einpoklum

"dolaylı olarak gerçekleşen bu tür şeyler" gerçek bir tuzak olduğu için olumsuz oydan çok olumlu oyla sonuçlandı.
Ian Ringrose

1
Bu gerçekten güzel bir örnek. OP onu dışlamak istemesine rağmen, bu muhtemelen mevcut kodda sessiz davranış değişikliklerine neden olabilecek en olası şeylerden biridir . +1
cdhowie

1
@TedLyngmo Dedektörü tamir edemezseniz, tespit edilen şeyi değiştirin. Texas keskin nişancılık!
Yakk - Adam Nevraumont

15

Oh boy ... bağlantı cpplearner sağlanan olduğu korkutucu .

Diğerlerinin yanı sıra, C ++ 20, C ++ yapılarının C tarzı yapı bildirimine izin vermedi.

typedef struct
{
  void member_foo(); // Ill-formed since C++20
} m_struct;

Eğer size böyle yapılar yazmayı öğretmiş olsaydınız (ve "C'yi sınıflarla birlikte" öğreten insanlar tam olarak bunu öğretirlerse), mahvoldunuz demektir .



19
@ Peter-ReinstateMonica Ben her zaman typedefyapılarım ve kesinlikle tebeşirimi bunun üzerine boşa harcamayacağım. Bu kesinlikle bir zevk meselesidir ve sizin bakış açınızı paylaşan oldukça etkili insanlar (Torvalds ...) varken, benim gibi diğer insanlar, ihtiyaç duyulan tek şeyin türler için bir adlandırma konvansiyonu olduğuna işaret edeceklerdir. Kodu structanahtar kelimelerle karıştırmak, büyük harfin ( MyClass* object = myClass_create();) ifade etmeyeceği anlayışına çok az şey katar . structKodunuzda olmasını istiyorsanız buna saygı duyuyorum . Ama benimkinde istemiyorum.
cmaster - reinstate monica

5
Bununla birlikte, C ++ 'yı programlarken, structyalnızca düz eski veri türleri ve classüye işlevleri olan her şey için kullanmak gerçekten iyi bir kuraldır . Ancak bu kuralı C'de kullanamazsınız, çünkü classC'de yoktur .
cmaster - monica'yı

1
@ Peter-ReinstateMonica Evet, C de sözdizimsel olarak bir yöntem ekleyemezsiniz, ancak bu C'nin structaslında POD olduğu anlamına gelmez . C kodunu yazma şeklim, çoğu yapıya yalnızca tek bir dosyadaki kod ve sınıflarının adını taşıyan işlevler dokunuyor. Temelde sözdizimsel şekersiz OOP. Bu, a'nın içinde neyin değiştiğini structve üyeleri arasında hangi değişmezlerin garanti edildiğini gerçekten kontrol etmemi sağlıyor . Bu yüzden, structsüye işlevlerine, özel uygulamaya, değişmezlere ve veri üyelerinden soyutlara sahip olma eğilimindeyim. POD gibi gelmiyor, değil mi?
cmaster - reinstate monica

5
extern "C"Bloklarda yasak olmadıkları sürece , bu değişiklikle ilgili herhangi bir sorun görmüyorum. C ++ 'da hiç kimse yapıları yazmamalıdır. Bu, C ++ 'nın Java'dan farklı anlambilimlere sahip olmasından daha büyük bir engel değildir. Yeni bir programlama dili öğrendiğinizde, bazı yeni alışkanlıklar öğrenmeniz gerekebilir.
Cody Grey

15

C ++ 03'te 3, C ++ 11'de 0 yazan bir örnek aşağıda verilmiştir:

template<int I> struct X   { static int const c = 2; };
template<> struct X<0>     { typedef int c; };
template<class T> struct Y { static int const c = 3; };
static int const c = 4;
int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }

Davranıştaki bu değişiklik, için özel işlemden kaynaklanmıştır >>. C ++ 11'den önce >>her zaman doğru vardiya operatörüydü. C ++ 11 ile >>de bir şablon bildiriminin parçası olabilir.


Teknik olarak bu doğrudur, ancak bu >>şekilde kullanılması nedeniyle bu kod başlangıçta "gayri resmi olarak belirsizdi" .
einpoklum

11

Trigraflar düştü

Kaynak dosyalar , standartta tanımlanan kaynak karakter kümesine uygulama tanımlı bir şekilde eşlenen fiziksel bir karakter kümesinde kodlanır . Kaynak karakter kümesinin ihtiyaç duyduğu tüm noktalama işaretlerine yerel olarak sahip olmayan bazı fiziksel karakter kümelerinden eşlemeleri barındırmak için, dil tanımlı trigraflar — daha az yaygın bir noktalama karakteri yerine kullanılabilecek üç ortak karakter dizileri. Önişlemci ve derleyicinin bunları ele alması gerekiyordu.

C ++ 17'de trigraflar kaldırıldı. Bu nedenle, bazı kaynak dosyalar, ilk önce fiziksel karakter kümesinden kaynak karakter kümesiyle bire bir eşleyen başka bir fiziksel karakter kümesine çevrilmedikçe yeni derleyiciler tarafından kabul edilmeyecektir. (Uygulamada, çoğu derleyici, trigrafların yorumlanmasını isteğe bağlı yaptı.) Bu, ince bir davranış değişikliği değildir, ancak, önceden kabul edilebilir kaynak dosyaların harici bir çeviri işlemi olmadan derlenmesini engelleyen bir değişikliktir.

Daha fazla kısıtlama char

Standart ayrıca , uygulama tanımlı olan, ancak en azından tüm kaynak karakter setini ve az sayıda kontrol kodunu içermesi gereken yürütme karakter setine atıfta bulunur .

C ++ standardı char, yürütme karakter kümesindeki her değeri verimli bir şekilde temsil edebilen, muhtemelen işaretsiz bir integral türü olarak tanımlanır . Bir dil avukatının sunumu ile a'nın charen az 8 bit olması gerektiğini iddia edebilirsiniz .

Uygulamanız için işaretsiz bir değer kullanıyorsa char, 0 ile 255 arasında değişebileceğini ve bu nedenle olası her bayt değerini depolamak için uygun olduğunu bilirsiniz.

Ancak uygulamanız imzalı bir değer kullanıyorsa, seçenekleri vardır.

Çoğu, ikinin tamamlayıcısını kullanır charve minimum -128 ila 127 aralığı verir. Bu 256 benzersiz değerdir.

Ancak başka bir seçenek işaret + büyüklük idi, burada bir bit, sayının negatif olup olmadığını ve diğer yedi bitin büyüklüğü belirtmek için ayrılmıştır. Bu char, yalnızca 255 benzersiz değer olan -127 ila 127 aralığı verir. (Çünkü -0'ı temsil edecek kullanışlı bir bit kombinasyonunu kaybedersiniz.)

Emin komite şimdiye açıkça bir kusur olarak bu belirlenen değilim ama gelen ring seferi garanti etmek standarda dayanmayan çünkü öyleydi unsigned chariçin charve tekrar orijinal değerini koruyacak. (Pratikte, tüm uygulamalar bunu yaptı çünkü hepsi imzalı integral türleri için ikinin tamamlayıcısını kullanıyordu.)

Sadece yakın zamanda (C ++ 17?), Gidiş-dönüşü sağlamak için ifade düzeltildi. Bu düzeltme, diğer tüm gereksinimlerle birlikte char, charaçıkça belirtmeden ikinin tamamlayıcısını etkili bir şekilde zorunlu kılar (standart, diğer işaretli integral türleri için işaret + büyüklük gösterimlerine izin vermeye devam etse bile). Tüm işaretli integral türlerinin ikinin tümlemesini kullanmasını gerektiren bir teklif var, ancak C ++ 20'ye girip girmediğini hatırlamıyorum.

Yani bu, aradığınız şeyin tam tersi çünkü önceden yanlış olan aşırı küstah koda geriye dönük bir düzeltme sağlıyor.


Trigraflar kısmı bu sorunun cevabı değil - bu sessiz bir değişiklik değil. Ve IIANM, ikinci bölüm, tam olarak zorunlu davranış olarak tanımlanan uygulama değişikliğidir, ki bu da benim sorduğum şey değil.
einpoklum

10

Bunu doğru kod için bir kırılma değişikliği olarak kabul edip etmediğinizden emin değilim, ama ...

C ++ 11'den önce, derleyicilerin kopya oluşturucu gözlenebilir yan etkilere sahip olsa bile belirli koşullarda kopyaları çıkarmasına izin veriliyordu, ancak gerekli değildi. Şimdi kopya seçimini garantiledik. Davranış esas olarak uygulama tanımlı durumdan gerekli hale geldi.

Bu, kopya oluşturucunuzun yan etkilerinin eski sürümlerde ortaya çıkmış olabileceği , ancak yeni sürümlerde asla ortaya çıkmayacağı anlamına gelir. Doğru kodun uygulama tanımlı sonuçlara dayanmaması gerektiğini savunabilirsiniz, ancak bunun bu kodun yanlış olduğunu söylemekle tamamen aynı olduğunu düşünmüyorum.


1
Bu "gereksinim" C ++ 17'de eklendi, C ++ 11 değil mi? ( Geçici materyalizasyona bakın .)
cdhowie

@cdhowie: Sanırım haklısın. Bunu yazarken standartlara sahip değildim ve muhtemelen bazı arama sonuçlarıma çok fazla güveniyordum.
Adrian McCarthy

Uygulama tanımlı davranışta bir değişiklik bu sorunun cevabı olarak sayılmaz.
einpoklum

7

Bir akıştan (sayısal) veri okurken ve okuma başarısız olurken davranış, c ++ 11'den itibaren değiştirildi.

Örneğin, bir tamsayı içermeyen bir akıştan bir tamsayı okumak:

#include <iostream>
#include <sstream>

int main(int, char **) 
{
    int a = 12345;
    std::string s = "abcd";         // not an integer, so will fail
    std::stringstream ss(s);
    ss >> a;
    std::cout << "fail = " << ss.fail() << " a = " << a << std::endl;        // since c++11: a == 0, before a still 12345 
}

C ++ 11 başarısız olduğunda okuma tamsayısını 0 olarak ayarlayacağından; c ++ <11'de tamsayı değiştirilmedi. Bununla birlikte, gcc, standardı c ++ 98'e geri zorlarken bile (-std = c ++ 98 ile) en azından 4.4.7 sürümünden beri her zaman yeni davranış gösterir.

(Imho eski davranış aslında daha iyiydi: neden değeri 0 olarak değiştirelim ki bu da tek başına geçerli, hiçbir şey okunamazken?)

Referans: bkz https://en.cppreference.com/w/cpp/locale/num_get/get


Ancak returnType ile ilgili herhangi bir değişiklik söz konusu değildir. C ++ 11'den beri yalnızca 2 haber aşırı yüklemesi mevcut
Yapı

Bu tanımlanmış davranış hem C ++ 98 hem de C ++ 11'de miydi? Yoksa davranış tanımlandı mı?
einpoklum

Cppreference.com sağ olduğunda: "Bir hata oluşursa, v değişmeden kalır. (C ++ 11'e kadar)" Yani davranış C ++ 11'den önce tanımlanmış ve değişmiştir.
DanRechtsaf

Anladığım kadarıyla, ss> a için davranış gerçekten tanımlanmıştı, ancak başlatılmamış bir değişkeni okuduğunuz çok yaygın durumda, c ++ 11 davranışı, tanımlanmamış bir davranış olan başlatılmamış bir değişken kullanacaktır. Bu nedenle, çok yaygın tanımlanmamış bir davranışa karşı hata korumalarında varsayılan yapı.
Rasmus Damgaard Nielsen
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.