C ++ 'ın Gizli Özellikleri? [kapalı]


114

"Gizli özellikler" soruları söz konusu olduğunda C ++ sevgisi yok mu? Onu oraya atacağımı düşündüm. C ++ 'ın gizli özelliklerinden bazıları nelerdir?


@Devtron - Özellik olarak satılan bazı harika hatalar (yani beklenmedik davranışlar) gördüm. Aslında, oyun endüstrisi bugünlerde bunu gerçekleştirmeye çalışıyor ve buna "acil oyun" diyor (ayrıca, Psi-Ops'tan "TK Surfing" e bakın, tamamen bir hataydı, sonra onu olduğu gibi bıraktılar ve onlardan biri oyunun en iyi özellikleri IMHO)
Grant Peters

5
@Laith J: 786 sayfalık ISO C ++ standardını çok fazla insan baştan sona okumadı - ama sanırım okudunuz ve hepsini sakladınız, değil mi?
j_random_hacker

2
@Laith, @j_random: "Ben onu tanımak, nasıl bir programcı şaka olduğunu ve ne isabetli tepki nedir" benim soruya bakın stackoverflow.com/questions/1/you-have-been-link-rolled .

Yanıtlar:


308

Çoğu C ++ programcısı, üçlü operatöre aşinadır:

x = (y < 0) ? 10 : 20;

Ancak, bunun bir değer olarak kullanılabileceğinin farkında değiller:

(a == 0 ? a : b) = 1;

hangisinin kısaltması

if (a == 0)
    a = 1;
else
    b = 1;

Dikkatle kullanın :-)


11
Çok ilginç. Okunamayan bazı kodlar yaptığını görebiliyorum.
Jason Baker

112
Amanın. (a == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Dunno 's GCC özgü olmadığını, ama bu da çalışmış bulmak için şaşırdı: (value ? function1 : function2)().
Chris Burt-Brown

3
@Chris Burt-Brown: Hayır, aynı türe sahiplerse (yani varsayılan argümanlar yoksa) function1ve function2dolaylı olarak işlev işaretçilerine dönüştürülürse ve sonuç örtük olarak geri dönüştürülürse her yerde çalışmalıdır .
MSalters

238

URI'leri C ++ kaynağına hatasız olarak yerleştirebilirsiniz. Örneğin:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Ama işlev başına sadece bir tane olduğundan şüpheleniyorum? :)
Constantin

51
@jpoh: http ve ardından iki nokta üst üste işareti, daha sonra bir goto ifadesinde kullanacağınız bir "etiket" olur. Yukarıdaki örnekte herhangi bir goto ifadesinde kullanılmadığı için derleyicinizden bu uyarıyı alırsınız.
utku_karatas

9
Farklı protokollere sahip oldukları sürece birden fazla ekleyebilirsiniz! ftp.microsoft.com gopher: //aerv.nl/1 ve benzeri ...
Daniel Earwicker

4
@Pavel: İki nokta üst üste ile izlenen bir tanımlayıcı bir etikettir ( gotoC ++ 'ın sahip olduğu kullanım için). İki eğik çizgiyi takip eden her şey bir yorumdur. Bu nedenle, with http://stackoverflow.com, httpbir etikettir (teorik olarak yazabilirsiniz goto http;) ve //stackoverflow.comsadece bir satır sonu yorumudur. Bunların her ikisi de yasal C ++ 'dır, bu nedenle yapı derlenir. Elbette belirsiz bir şekilde yararlı hiçbir şey yapmaz.
David Thornley

8
Maalesef goto http;aslında URL'yi takip etmiyor. :(
Yakov Galka

140

İşaretçi aritmetiği.

C ++ programcıları, ortaya çıkabilecek hatalar nedeniyle işaretçilerden kaçınmayı tercih eder.

Yine de gördüğüm en havalı C ++? Analog değişmez değerler.


11
Hatalar yüzünden işaretçilerden kaçınıyor muyuz? İşaretçiler temelde dinamik C ++ kodlamasının olduğu her şeydir!
Nick Bedford

1
Analog değişmez değerler, karmaşık C ++ yarışma girişleri, özellikle ASCII-art türü için harikadır.
Synetech

119

Buradaki çoğu gönderiye katılıyorum: C ++ çok paradigmalı bir dildir, bu nedenle bulacağınız "gizli" özellikler (ne pahasına olursa olsun kaçınmanız gereken "tanımlanmamış davranışlar" dışında), tesislerin akıllıca kullanımlarıdır.

Bu tesislerin çoğu dilin yerleşik özellikleri değil, kütüphane tabanlı özelliklerdir.

En önemlisi, C dünyasından gelen C ++ geliştiricileri tarafından yıllarca görmezden gelinen RAII'dir . Operatör aşırı yükleme , genellikle hem dizi benzeri davranışı (alt simge operatörü), işaretçi benzeri işlemleri (akıllı işaretçiler) hem de yerleşik benzeri işlemleri (matrisleri çarpma) etkinleştiren yanlış anlaşılan bir özelliktir.

Kullanımı haricinde genellikle zordur, ama bazı çalışma yoluyla gerçekten sağlam kod üretebilir istisna güvenliği başarısız olmayacak olan veya bir taahhüt benzeri olduğunu başaracaktır olduğu özellikler, ya da Geri döndürme arka olacak kodu dahil özelliklerine ( orijinal durumu).

C ++ 'ın en ünlü "gizli" özelliği şablon meta programlamadır , çünkü programınızın çalışma zamanı yerine derleme zamanında kısmen (veya tamamen) çalıştırılmasını sağlar. Yine de bu zordur ve denemeden önce şablonları sağlam bir şekilde kavramanız gerekir.

Diğerleri, C ++ 'ın atası olan C dışında "programlama yolları" üretmek için çoklu paradigmayı kullanır.

Functors kullanarak , ek tür güvenliği ve durum bilgisi olan işlevleri simüle edebilirsiniz. Komut modelini kullanarak kod yürütmeyi geciktirebilirsiniz. Diğer tasarım modellerinin çoğu , "resmi C ++ paradigmaları" listesinde olmaması gereken alternatif kodlama stilleri üretmek için C ++ 'da kolayca ve verimli bir şekilde uygulanabilir.

Şablonları kullanarak , ilk başta düşündüğünüz değil, çoğu türde çalışacak kod üretebilirsiniz. Tip güvenliğini de artırabilirsiniz (otomatik tip güvenli malloc / realloc / ücretsiz gibi). C ++ nesne özellikleri gerçekten güçlüdür (ve bu nedenle dikkatsizce kullanılırsa tehlikelidir), ancak dinamik polimorfizmin bile statik sürümü C ++ ' dadır : CRTP .

Scott Meyers'den " Etkili C ++ " tipi kitapların veya Herb Sutter'ın " Olağanüstü C ++ " tipi kitapların hem okunması kolay hem de C ++ 'nın bilinen ve daha az bilinen özellikleri hakkında bilgi hazineleri olduğunu buldum .

Benim tercih ettiğim şey, herhangi bir Java programcısının saçını dehşetten yükseltmesi gereken bir şey: C ++ 'da, bir nesneye bir özellik eklemenin en nesneye yönelik yolu, üye yerine üye olmayan arkadaş olmayan bir işlevdir. işlev (yani sınıf yöntemi), çünkü:

  • C ++ 'da, bir sınıf' arabirimi hem üye işlevleri hem de aynı ad alanındaki üye olmayan işlevlerdir.

  • arkadaş olmayan üye olmayan işlevlerin dahili sınıfa ayrıcalıklı erişimi yoktur. Bu nedenle, üye olmayan arkadaş olmayan bir işlev yerine üye işlevi kullanmak, sınıfın kapsüllenmesini zayıflatacaktır.

Bu, deneyimli geliştiricileri bile şaşırtmaz.

(Kaynak: Diğerlerinin yanı sıra, Herb Sutter'ın 84 Haftanın çevrimiçi Gurusu: http://www.gotw.ca/gotw/084.htm )


+1 çok kapsamlı cevap. bariz nedenlerden dolayı eksiktir (aksi takdirde artık "gizli özellikler" olmazdı!): Cevabın sonundaki ilk noktada, bir sınıf arayüzünün üyelerinden bahsetmiştiniz. Yani ".. hem üye işlevleri hem de üye olmayan arkadaş işlevleri" mi?
wilhelmtell


1 ile ilgili bahsettiğiniz şey koenig araması olmalı, değil mi?
Özgür

1
@wilhelmtell: Hayır hayır hayır ... :-p ... "üye işlevleri ve ARKADAŞ OLMAYAN üye olmayan işlevleri" demek istiyorum .... Koenig'in Araması, bu işlevlerin diğerlerinden daha erken değerlendirilmesini sağlayacaktır " sembol arayışında "dış" işlevler
paercebal

7
Harika bir gönderi ve özellikle çok az insanın fark ettiği son bölüm için +1. Muhtemelen Boost kitaplığını da "gizli özellik" olarak eklerdim. Ben bunu C ++ 'nın sahip olması gereken standart kitaplık olarak görüyorum. ;)
jalf

118

Okulda geçirdiğim süre boyunca bunu hiç duymadığım için biraz gizli olduğunu düşündüğüm bir dil özelliği, ad alanı takma adıdır. Takviye belgelerinde bunun örneklerine rastlayana kadar dikkatimi çekmedi. Elbette, artık bunu bildiğime göre, herhangi bir standart C ++ referansında bulabilirsiniz.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
Sanırım kullanmak istemiyorsanız bu yararlıdır using.
Siqi Lin

4
İş parçacığı için güvenli olana karşı güvenli olmayan veya sürüm 1'e karşı 2'yi seçmek, uygulamalar arasında geçiş yapmanın bir yolu olarak da kullanışlıdır.
Tony Delroy

3
Büyük ad alanı hiyerarşileri olan çok büyük bir proje üzerinde çalışıyorsanız ve başlıklarınızın ad alanı kirliliğine neden olmasını istemiyorsanız (ve değişken bildirimlerinizin insan tarafından okunabilir olmasını istiyorsanız) özellikle yararlıdır.
Brandon Bohrer

102

Değişkenler yalnızca bir fordöngünün başlangıç ​​kısmında değil, aynı zamanda sınıflar ve işlevler de bildirilebilir.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Bu, farklı türlerde birden çok değişkene izin verir.


31
Bunu yapabileceğinizi bilmek güzel, ama şahsen gerçekten böyle bir şey yapmaktan kaçınmaya çalışırım. Çoğunlukla okuması zor olduğu için.
Zoomulator

2
Aslında, bu bağlamda işe yarayan şey bir çift kullanmaktır: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin iyi öyleyse, gizli özelliği düşürmek yerine VS2008'e karşı bir hata raporu oluşturmanızı tavsiye ederim. Bu açıkça derleyicinizin hatasıdır.
Johannes Schaub -

2
Hmm, msvc10'da da çalışmıyor. Ne kadar üzücü :(
avakar

2
@avakar aslında, gcc v4.6'da onu da reddetmeye neden olan bir hata ortaya çıkardı
Johannes Schaub - litb

77

Dizi operatörü ilişkiseldir.

A [8], * (A + 8) ile eşanlamlıdır. Ekleme ilişkisel olduğundan, bu ..... 8 [A] ile eşanlamlı olan * (8 + A) olarak yeniden yazılabilir.

Yararlı demedin ... :-)


15
Aslında, bu numarayı kullanırken, gerçekten ne tür kullandığınıza dikkat etmelisiniz. A [8] aslında 8. A iken, 8 [A] adres 8'den başlayan Ath tamsayısıdır. A bir bayt ise, bir hatanız vardır.
Vincent Robert

38
"çağrışımlı" derken "değişmeli" mi demek istiyorsun?
DarenW

28
Vincent, yanılıyorsun. TürüA hiç önemli değil. Örneğin, Abir vardı char*, kod hala geçerli olacaktır.
Konrad Rudolph

11
A'nın bir işaretçi olması gerektiğine ve sınıf aşırı yükleme operatörü [] olmamasına dikkat edin.
David Rodríguez - dribeas

15
Vincent, burada tek bir integral türü ve bir işaretçi türü olmalı ve ne C ne de C ++ hangisinin önce gittiğini umursamıyor.
David Thornley

73

Az bilinen bir şey, sendikaların da şablon olabileceğidir:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

Yapıcılara ve üye işlevlerine de sahip olabilirler. Kalıtımla ilgisi olan hiçbir şey (sanal işlevler dahil).


İlginç! Öyleyse, tüm üyeleri ilklendirmeniz gerekiyor mu? Son üyenin daha önceki üyelerin "üstüne" başlatılacağını ima ederek olağan yapı sırasını takip ediyor mu?
j_random_hacker

j_random_hacker oh, doğru bu çok saçma. İyi yakalama. bir yapı olacağı gibi yazdım. bekle düzelteceğim
Johannes Schaub - litb

Bu tanımlanmamış davranışa neden olmaz mı?
Greg Bacon

7
eğer @gbacon, evet bu tanımsız davranış çağırmak yapar Fromve Tobuna göre ayarlanır ve kullanılır. Böyle bir birleşim, tanımlanmış davranışla da kullanılabilir To(işaretsiz bir karakter dizisi veya bir başlangıç ​​dizisini paylaşan bir yapı ile From). Tanımlanmamış bir şekilde kullansanız bile, yine de düşük seviyeli işler için faydalı olabilir. Her neyse, bu bir birleşim şablonunun sadece bir örneğidir - şablonlu bir birleşmenin başka kullanımları olabilir.
Johannes Schaub -

3
Yapıcıya dikkat edin. Yalnızca ilk öğeyi oluşturmanız gerektiğini ve buna yalnızca C ++ 0x'de izin verildiğini unutmayın. Mevcut standart itibariyle, önemsiz şekilde inşa edilebilir tiplere bağlı kalmanız gerekir. Ve yıkıcı yok.
Potatoswatter

72

C ++ bir standarttır, hiçbir gizli özellik olmamalıdır ...

C ++ çok paradigmalı bir dildir, oradaki son paranızın gizli özellikler olduğuna bahse girebilirsiniz. Pek çok örnekten biri: şablon meta programlaması . Standartlar komitesindeki hiç kimse, derleme zamanında çalıştırılacak bir Turing-complete alt dili olmasını amaçlamadı.


65

C'de çalışmayan diğer bir gizli özellik, tekli +operatörün işlevselliğidir . Her türlü şeyi teşvik etmek ve çürütmek için kullanabilirsiniz

Bir Numaralandırmayı tamsayıya dönüştürme

+AnEnumeratorValue

Ve daha önce numaralandırma türüne sahip olan numaralandırıcı değeriniz artık değerine uyabilecek mükemmel tamsayı türüne sahiptir. Manuel olarak, bu türü neredeyse hiç tanımazsınız! Bu, örneğin numaralandırmanız için aşırı yüklenmiş bir operatör uygulamak istediğinizde gereklidir.

Bir değişkenden değeri alın

Sınıf dışı bir tanım olmadan sınıf içi statik başlatıcı kullanan bir sınıf kullanmanız gerekir, ancak bazen bağlanamaz? Operatör, türüne ilişkin varsayımlar veya bağımlılıklar yapmadan bir geçici oluşturmaya yardımcı olabilir

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

Bir diziyi bir işaretçiye dönüştürür

Bir işleve iki işaretçi iletmek istiyor ama işe yaramıyor mu? Operatör yardımcı olabilir

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

Const referanslara bağlı geçicilerin ömrü, çok az kişinin bildiği bir şeydir. Ya da en azından çoğu insanın bilmediği en sevdiğim C ++ bilgisi parçası.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
Detaylandırır mısın Sadece alay ettiğiniz gibi;)
Joseph Garvin

8
ScopeGuard ( ddj.com/cpp/184403758 ), bu özelliği kullanan harika bir örnektir.
MSN

2
Joseph Garvin ile birlikteyim. Lütfen bizi aydınlatın.
Peter Mortensen

Sadece yorumlarda yaptım. Ayrıca, const referans parametresi kullanmanın doğal bir sonucudur.
MSN


52

Sık kullanılmayan güzel bir özellik, işlev çapında deneme-yakalama bloğudur:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

Ana kullanım, istisnayı diğer istisna sınıfına çevirmek ve yeniden atmak veya istisnalar ve dönüş tabanlı hata kodu işlemeyi arasında çevirmek olacaktır.


returnFunction Try bloğunu yakalayabileceğinizi sanmıyorum , sadece yeniden atabilirsiniz.
Constantin

Yukarıdakileri derlemeyi denedim ve hiçbir uyarı vermedi. Yukarıdaki örneğin işe yaradığını düşünüyorum.
vividos

7
dönüş sadece kurucular için yasaklanmıştır. Bir kurucunun fonksiyon try bloğu, tabanı ve üyeleri başlatan hataları yakalayacaktır (try bloğunun fonksiyonun içinde bir try bloğundan farklı bir şey yaptığı tek durum); tekrar atmamak eksik bir nesneye neden olur.
puetzk

Evet. Bu çok kullanışlıdır. İstisnaları yakalamak ve HRESULTS döndürmek için BEGIN_COM_METHOD ve END_COM_METHOD makroları yazdım, böylece istisnalar bir COM arayüzü uygulayan bir sınıftan dışarı sızmaz. İyi çalıştı.
Scott Langham

3
@Puetzk tarafından belirtildiği gibi, bu, temel sınıfların oluşturucuları veya veri üyelerinin oluşturucuları gibi bir yapıcının başlatıcı listesindeki herhangi bir şey tarafından atılan istisnaları ele almanın tek yoludur .
anton.burger

44

Birçoğu biliyor identity / idmeta işlevini bilir, ancak şablon olmayan durumlarda bunun için güzel bir kullanım durumu vardır: Bildirimleri yazma kolaylığı:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

C ++ bildirimlerinin şifresinin çözülmesine büyük ölçüde yardımcı olur!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

İlginç, ama başlangıçta bu tanımlardan bazılarını okumakta daha çok sorun yaşadım . C ++ bildirimleriyle ilgili içten dışa sorunu çözmenin bir başka yolu, bazı şablon türü takma adları yazmaktır: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);veya pointer<void(int)> f(pointer<void()>);veyafunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

Oldukça gizli bir özellik, değişkenleri bir if koşulu içinde tanımlayabilmeniz ve kapsamının yalnızca if ve else bloklarını kapsayabilmesidir:

if(int * p = getPointer()) {
    // do something
}

Bazı makrolar, örneğin aşağıdaki gibi bazı "kilitli" kapsamlar sağlamak için bunu kullanır:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

Ayrıca BOOST_FOREACH bunu başlık altında kullanır. Bunu tamamlamak için, yalnızca eğer bir durumda değil, aynı zamanda bir anahtarla da mümkündür:

switch(int value = getIt()) {
    // ...
}

ve bir süre döngüsünde:

while(SomeThing t = getSomeThing()) {
    // ...
}

(ve ayrıca bir for durumunda). Ama bunların hepsinin bu kadar yararlı olup olmadığından pek emin değilim :)


Temiz! Bunu yapabileceğinizi hiç bilmiyordum ... hata dönüş değerleri ile kod yazarken biraz güçlük çeker (ve kurtarır). Bu formda sadece! = 0 yerine bir koşullu olmanın bir yolu var mı? eğer ((int r = func ()) <0) işe yaramıyorsa ...
puetzk

puetzk, hayır yok. ama beğendiğine sevindim :)
Johannes Schaub - litb

4
@Frerich, bu C kodunda hiç mümkün değil. Sanırım düşünüyorsunuz if((a = f()) == b) ..., ancak bu cevap aslında koşulda bir değişken ilan ediyor.
Johannes Schaub - litb

1
@Mesela çok farklı, çünkü değişken bildirimi boole değeri için hemen test ediliyor. For-döngüler için de bir eşleme var, bu doğru for(...; int i = foo(); ) ...;olduğu sürece gövdeden geçecek ve iher seferinde yeniden başlatacak gibi görünüyor . Gösterdiğiniz döngü basitçe bir değişken bildirimini gösteriyor, ancak aynı anda bir koşul olarak hareket eden bir değişken bildirimi değil :)
Johannes Schaub - litb

5
Çok iyi, ancak bu özelliğin kullanım amacının dinamik işaretçiler için olduğunu söylemediniz, inanıyorum.
mmocny

29

Virgül operatörünün operatör aşırı yüklerini çağırmasını önleme

Bazen virgül operatörünü geçerli bir şekilde kullanırsınız, ancak kullanıcı tanımlı virgül operatörünün araya girmediğinden emin olmak istersiniz, çünkü örneğin sol ve sağ taraf arasındaki sıra noktalarına güvenirsiniz veya hiçbir şeyin istenen şeyi engellemediğinden emin olmak istersiniz aksiyon. İşte burada void()devreye giriyor:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Koşul ve kod için koyduğum yer tutucuları görmezden gelin. Önemli void()olan, derleyiciyi yerleşik virgül operatörünü kullanmaya zorlayan şeydir. Bu, bazen özellik sınıflarını uygularken de yararlı olabilir.



28

Yapıcıda dizi başlatma. Örneğin bir sınıfta aşağıdaki gibi bir dizimiz varsa int:

class clName
{
  clName();
  int a[10];
};

Yapıcıdaki dizideki tüm öğeleri varsayılan değerine (burada dizinin tüm öğeleri sıfıra) şu şekilde başlatabiliriz:

clName::clName() : a()
{
}

6
Bunu herhangi bir yerde herhangi bir dizi ile yapabilirsiniz.
Potatoswatter

@Potatoswatter: En can sıkıcı çözümleme nedeniyle göründüğünden daha zor. Belki bir dönüş değeri dışında yapılabileceği başka bir yer
düşünemiyorum

Dizinin türü bir sınıf türü ise, bu gerekli değil değil mi?
Thomas Eding

27

Oooh, bunun yerine evcil hayvan nefretlerinin bir listesini verebilirim:

  • Polimorfik olarak kullanmak istiyorsanız yıkıcıların sanal olması gerekir
  • Bazen üyeler varsayılan olarak başlatılır, bazen değildir
  • Yerel sınıflar şablon parametreleri olarak kullanılamaz (onları daha az kullanışlı hale getirir)
  • istisna belirleyiciler: kullanışlı görünseler de değil
  • işlev aşırı yüklemeleri, farklı imzalara sahip temel sınıf işlevleri gizler.
  • uluslararasılaştırmada yararlı bir standardizasyon yok (taşınabilir standart geniş karakter kümesi, herkes? C ++ 0x'e kadar beklememiz gerekecek)

Artı tarafta

  • gizli özellik: fonksiyon deneme blokları. Ne yazık ki bunun için bir kullanım bulamadım. Evet, neden eklediklerini biliyorum ama anlamsız kılan bir kurucuda yeniden atmanız gerekiyor.
  • Konteyner değişikliğinden sonra yineleyici geçerliliği hakkındaki STL garantilerine dikkatle bakmaya değer, bu da biraz daha hoş döngüler oluşturmanıza izin verebilir.
  • Güçlendirme - bu bir sır değil ama kullanmaya değer.
  • Dönüş değeri optimizasyonu (açık değil, ancak standart tarafından özellikle izin verilir)
  • Functors aka function nesneleri aka operator (). Bu, STL tarafından yaygın olarak kullanılmaktadır. gerçekten bir sır değil, ancak operatörün aşırı yüklenmesi ve şablonların şık bir yan etkisidir.

16
evcil hayvan nefreti: C ++ uygulamaları için tanımlanmış ABI yok, herkesin kullandığı C'nin aksine, çünkü her dil bir C işlevini çağırmayı garanti edebilir, hiç kimse C ++ için aynısını yapamaz.
gbjbaanb

8
İmha edicilerin yalnızca polimorfik olarak yok etmek istiyorsanız sanal olmaları gerekir, bu da ilk noktadan biraz farklıdır.
David Rodríguez - dribeas

2
C ++ 0x ile yerel türler şablon parametreleri olarak kullanılabilir.
tstenner

1
C ++ 0x ile, nesnenin herhangi bir sanal işlevi varsa (yani bir vtable) yıkıcılar sanal olacaktır.
Macke

NRVO'yu unutmayın ve tabii ki program çıktısını değiştirmediği sürece herhangi bir optimizasyona izin verilir
jk.

26

Herhangi bir sınıfın korumalı verilerine ve işlev üyelerine, tanımlanmamış davranışlar olmadan ve beklenen anlamlarla erişebilirsiniz. Nasıl olduğunu görmek için okumaya devam edin. Hata raporunu da okuyun ilgili .

Normalde, C ++ bir sınıfın nesnesinin statik korumalı olmayan üyelerine, bu sınıf sizin temel sınıfınız olsa bile erişmenizi yasaklar.

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

Bu yasak: Siz ve derleyici referansın gerçekte neye işaret ettiğini bilmiyorsunuz. Bir Cnesne olabilir , bu durumda sınıfın Bhiçbir işi ve verileri hakkında ipucu yoktur. Bu tür bir erişim, yalnızca xtüretilmiş bir sınıfa veya ondan türetilmiş bir sınıfa referans ise verilir . Ayrıca, üyeleri okuyan bir "atma" sınıfı oluşturarak, rastgele kod parçasının herhangi bir korumalı üyeyi okumasına izin verebilir, örneğin std::stack:

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Elbette, gördüğünüz gibi bu çok fazla hasara neden olur. Ancak şimdi, üye işaretçiler bu korumayı atlatmaya izin veriyor! Buradaki kilit nokta, üye göstericinin türünün , adresi alırken belirttiğiniz sınıfa değil , söz konusu üyeyi gerçekten içeren sınıfa bağlı olmasıdır . Bu, kontrolü atlatmamızı sağlar

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

Ve tabii ki std::stackörnekle de işe yarıyor .

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Bu, türetilmiş sınıftaki, üye adını genel yapan ve temel sınıfın üyesine gönderme yapan bir kullanım bildirimi ile daha da kolay olacaktır.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

Diğer bir gizli özellik, işlev işaretçilerine veya referanslara dönüştürülebilen sınıf nesnelerini çağırabilmenizdir. Aşırı yük çözümü, bunların sonucunda yapılır ve argümanlar mükemmel bir şekilde iletilir.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

Bunlara "vekil çağrı işlevleri" denir.


1
Bunların sonucunda aşırı yük çözümlemesi yapıldığını söylediğinizde, bunu aslında her iki Functor'a dönüştürdüğünü ve sonra aşırı yük çözüldüğünü mü kastediyorsunuz? Func1 * () operatöründe ve Func2 * () operatöründe bir şey yazdırmayı denedim, ancak hangi dönüşüm operatörünün çağrılacağını anladığında doğru olanı seçiyor gibi görünüyor.
navigator

3
@navigator, evet kavramsal olarak her ikisine de dönüştürür ve sonra en iyisini seçer. Aslında onları çağırmasına gerek yoktur, çünkü sonuç türünden zaten ne vereceğini bilir. Gerçek arama, sonunda neyin seçildiği ortaya çıktığında yapılır.
Johannes Schaub - litb

26

Gizli özellikler:

  1. Saf sanal işlevlerin uygulaması olabilir. Yaygın örnek, saf sanal yıkıcı.
  2. Bir işlev, istisna belirtimlerinde listelenmeyen bir istisna atarsa, ancak işlevin std::bad_exceptionistisna belirtiminde varsa, istisna std::bad_exceptionotomatik olarak dönüştürülür ve atılır. Bu şekilde en azından bad_exceptiona'nın atıldığını bileceksiniz . Daha fazlasını buradan okuyun .

  3. fonksiyon deneme blokları

  4. Bir sınıf şablonunda açıklayıcı typedef'lerdeki şablon anahtar sözcüğü. Üye şablon uzmanlık adı sonra görünürse ., ->veya ::operatör ve bu ismi açıkça nitelikli şablon parametreleri, anahtar kelime şablonla önek üye şablon adı vardır. Daha fazlasını buradan okuyun .

  5. fonksiyon parametre varsayılanları çalışma zamanında değiştirilebilir. Daha fazlasını buradan okuyun .

  6. A[i] kadar iyi çalışıyor i[A]

  7. Bir sınıfın geçici örnekleri değiştirilebilir! Sabit olmayan bir üye işlevi geçici bir nesnede çağrılabilir. Örneğin:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    Daha fazlasını buradan okuyun .

  8. :Üçlü ( ?:) operatör ifadesinden önce ve sonra iki farklı tür varsa , sonuçta ortaya çıkan ifade türü, ikisi arasında en genel olanıdır. Örneğin:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Baba: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Değiştirmeyi anlıyorum, bu sadece [] 'nin kendi anlamsal değerine sahip olmadığı ve basitçe "x [y]" ile "(* ((x) + (y )))". Hiç beklediğim gibi değil. Neden bu şekilde tanımlandığını merak ediyorum.
P Baba

C ile geriye dönük uyumluluk
jmucchiello

2
İlk noktanızla ilgili olarak: Saf bir sanal işlevi uygulamanız gereken belirli bir durum var : saf sanal yıkıcılar.
Frerich Raabe

24

map::operator[]anahtar eksikse girdi oluşturur ve varsayılan olarak oluşturulmuş girdi değerine başvuru döndürür. Böylece yazabilirsiniz:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

Kaç C ++ programcısının bunu bilmediğine hayret ediyorum.


11
Ve diğer tarafta bir Const haritasında [] işlecini kullanamazsınız
David Rodríguez - dribeas

2
Nick için +1, insanlar bilmiyorlarsa çıldırabilir .find().
LiraNuna

veya " const map::operator[]hata mesajları üretir"
sadece biri

2
Dilin bir özelliği değil, Standart şablon kitaplığının bir özelliğidir. Ayrıca [] operatörü geçerli bir referans döndürdüğü için oldukça açıktır.
Ramon Zarazua B.

2
Bunun bir özellik olduğunu anlamak için haritaları C # 'da kullanmak zorunda kaldım, çünkü haritalar bu şekilde davranmaz. Kullandığımdan daha fazla rahatsız olduğumu düşündüm ama yanılmışım gibi görünüyor. C # 'da özlüyorum.
sbi

20

Fonksiyonların veya değişkenlerin isimsiz bir ad alanına yerleştirilmesi, staticbunların dosya kapsamıyla sınırlandırılmasının kullanımını ortadan kaldırır.


"kullanımdan kaldırılıyor" güçlü bir terimdir…
Potatoswatter

@Potato: Eski yorum, biliyorum, ancak standart ad alanı kapsamında statik kullanımının, adsız ad alanları tercihiyle kullanımdan kaldırıldığını söylüyor.
GManNickG

@GMan: Sorun yok, SO sayfalarının gerçekten "öldüğünü" düşünmüyorum. Sadece hikayenin her iki tarafı için, staticküresel kapsamda hiçbir şekilde eskimiş değil. (Referans için: C ++ 03 §D.2)
Potatoswatter

Ah, daha yakından okurken, "Global ad alanında bildirilen bir ad, global ad alanı kapsamına sahiptir (global kapsam olarak da adlandırılır)." Bu gerçekten bunun anlamı mı?
Potatoswatter

@ Patates: Evet. :) statickullanım yalnızca bir sınıf türü veya işlev içinde kullanılmalıdır.
GManNickG

19

Sınıf şablonlarında sıradan arkadaş işlevlerinin tanımlanması özel dikkat gerektirir:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

Bu örnekte, iki farklı örnekleme iki özdeş tanım oluşturur - ODR'nin doğrudan ihlali

Bu nedenle, sınıf şablonunun şablon parametrelerinin o şablonda tanımlanan herhangi bir arkadaş işlevi türünde göründüğünden emin olmalıyız (belirli bir dosyada bir sınıf şablonunun birden fazla somutlaştırılmasını önlemek istemiyorsak, ancak bu pek olası değildir). Bunu önceki örneğimizin bir varyasyonuna uygulayalım:

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

Sorumluluk Reddi: Bu bölümü C ++ Şablonlarından yapıştırdım : Tam Kılavuz / Bölüm 8.4


18

void fonksiyonları void değerleri döndürebilir

Az biliniyor, ancak aşağıdaki kod yeterli

void f() { }
void g() { return f(); }

Aşağıdaki tuhaf görünümlü biri kadar

void f() { return (void)"i'm discarded"; }

Bunu bilerek, bazı alanlarda yararlanabilirsiniz. Bir örnek: voidişlevler bir değer döndüremez, ancak siz de hiçbir şey döndüremezsiniz, çünkü bunlar void olmayan şekilde başlatılabilir. Değeri yerel bir değişkene depolamak yerine, bu bir hataya neden olur void, doğrudan bir değer döndürmeniz yeterlidir.

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

Bir dosyayı dizelerden oluşan bir vektör olarak okuyun:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Veya: vektör <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
yani vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- ikinci
paramdan

1
Bu gerçekten gizli bir C ++ özelliği değildir. Bir STL özelliğinden daha fazlası. STL! = Bir dil
Nick Bedford

14

Bit alanlarını şablonlayabilirsiniz.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Henüz bunun için herhangi bir amaç bulamadım, ama kesinlikle beni şaşırttı.


1
Yakın zamanda n-bit aritmetik için önerdiğim yere bakın: stackoverflow.com/questions/8309538/…
sehe

14

Herhangi bir programlama dilinin en ilginç gramerlerinden biri.

Bunlardan üçü birbirine ait ve ikisi tamamen farklı bir şey ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Üçüncü ve beşinci hariç tümü SomeTypeyığın üzerinde bir nesneyi tanımlar ve onu başlatır ( uilk iki durumda ve varsayılan kurucu dördüncü durumdadır. Üçüncüsü, parametre almayan ve a döndüren bir işlev bildirmektir SomeType. Beşincisi de benzer şekilde bildirir SomeTypeadlandırılmış türün değerine göre bir parametre alan bir işlev u.


1. ve 2. arasında herhangi bir fark var mı? yine de ikisinin de ilklendirme olduğunu biliyorum.
Özgür

Comptrol: Sanmıyorum. Her ikisi de sonunda kopyalama yapıcısını çağırır, ilki atama işleci gibi LOOKS olsa da, gerçekte kopya-yapıcıdır.
abelenky

1
U, SomeType'tan farklı bir türse, ilk önce dönüştürme yapıcısını ve sonra kopya oluşturucuyu çağırır, ikincisi ise yalnızca dönüştürme kurucusunu çağırır.
Eclipse

3
Birincisi yapıcının örtük çağrısı, ikincisi açık çağrıdır. Farkı görmek için aşağıdaki koda bakın: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "çift" << std :: endl; }; }; int ana () {sss ddd (7); // int yapıcı sss xxx = 7'yi çağırır; // çift yapıcı dönüş 0'ı çağırır; }
Kirill V. Lyadvinsky

Doğru - yapıcı açıkça bildirilmişse ilk satır çalışmayacaktır.
Eclipse

12

İleriye dönük beyanlardan kurtulmak:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

?: Operatörler ile anahtar ifadeleri yazma:

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Her şeyi tek bir satırda yapmak:

void a();
int b();
float c = (a(),b(),1.0f);

Memset olmadan yapıları sıfırlama:

FStruct s = {0};

Açı ve zaman değerlerini normalleştirme / sarma:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Referans atama:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};daha da kısadır.
Constantin

Son örnekte, şu şekilde daha basit olacaktır: a (); b (); şamandıra c = 1.0f;
Zifre

2
Bu sözdizimi "float c = (a (), b (), 1.0f);" atama işlemini vurgulamak için kullanışlıdır ("c" nin atanması). Atama işlemleri programlamada önemlidir çünkü kullanımdan kaldırılmış IMO olma olasılıkları daha düşüktür. Nedenini bilmiyorum, program durumunun her çerçeveye yeniden atandığı fonksiyonel programlama ile ilgili bir şey olabilir. PS. Ve hayır, "int d = (11,22,1.0f)" "1" e eşit olacaktır. Bir dakika önce VS2008 ile test edildi.
AareP

2
1 Eğer gerekmiyor çağıran main ? Ben öneririm global().main();(ve sadece tekil unutun size 's ömrünü aldığı, geçici sadece iş genişletilmiş olabilir )
sehe

1
Referans atamanın taşınabilir olduğundan şüpheliyim. Yapının ileriye dönük bildirimlerden feragat etmesini seviyorum.
Thomas Eding

12

Üçlü koşullu operatör ?:, ikinci ve üçüncü işleneninin "uygun" tiplere sahip olmasını gerektirir (gayri resmi olarak konuşur). Ancak bu gereksinimin bir istisnası vardır (punto amaçlanmıştır): ya ikinci ya da üçüncü işlenen void, diğer işlenenin türüne bakılmaksızın bir atma ifadesi (türü olan ) olabilir.

Başka bir deyişle, ?:operatör kullanılarak aşağıdaki tam olarak geçerli C ++ ifadeleri yazılabilir.

i = a > b ? a : throw something();

BTW, atma ifadesinin aslında bir ifade (tipte void) olduğu ve bir ifade olmadığı gerçeği , C ++ dilinin az bilinen bir başka özelliğidir. Bu, diğer şeylerin yanı sıra, aşağıdaki kodun tamamen geçerli olduğu anlamına gelir

void foo()
{
  return throw something();
}

Bu şekilde yapmanın pek bir anlamı olmasa da (belki bazı genel şablon kodlarında bu kullanışlı olabilir).


Ne olursa olsun, Neil'in bununla ilgili bir sorusu var: stackoverflow.com/questions/1212978/… , sadece ekstra bilgi için.
GManNickG

12

Hakimiyet kuralı kullanışlıdır, ancak çok az bilinir. Bir temel sınıf kafesi boyunca benzersiz olmayan bir yolda bile, kısmen gizli bir üye için ad aramanın, üye sanal bir temel sınıfa aitse benzersiz olduğunu söylüyor:

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

Bunu hizalama desteğini uygulamak için kullandım , hakimiyet kuralı aracılığıyla en katı hizalamayı otomatik olarak çözen hizalama .

Bu sadece sanal işlevler için değil, aynı zamanda typedef adları, statik / sanal olmayan üyeler ve diğer her şey için de geçerlidir. Meta programlarda üzerine yazılabilir özellikler uygulamak için kullanıldığını gördüm.


1
Temiz. Örneğinize dahil ettiğiniz belirli bir neden struct C...? Şerefe.
Tony Delroy
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.