Operatörün aşırı yüklenmesi için temel kurallar ve deyimler nelerdir?
Not: Yanıtlar belirli bir sırayla verilmiştir , ancak birçok kullanıcı yanıtları verildikleri zamana göre değil oylara göre sıraladığından, yanıtların en anlamlı oldukları sıraya göre dizini aşağıda verilmiştir :
- C ++ 'da operatör aşırı yüklemesinin Genel Sözdizimi
- C ++ 'da Operatör Aşırı Yüklemesinin Üç Temel Kuralı
- Üye ve Üye Olmayan arasındaki Karar
- Ortak operatörler aşırı yüklenecek
- Atama operatörü
- Giriş ve Çıkış Operatörleri
- İşlev çağrısı operatörü
- Karşılaştırma operatörleri
- Aritmetik operatörler
- Dizi Aboneliği
- İşaretçi Benzeri Türler için Operatörler
- Dönüşüm Operatörleri
- Yeniyi aşırı yükleme ve silme
Yanıtlar
Ortak operatörler aşırı yüklenecek
Aşırı yükleme operatörlerinde yapılan işlerin çoğu kazan plakası kodudur. Bu biraz şaşırtıcıdır, çünkü operatörler yalnızca sözdizimsel şeker olduğundan, gerçek çalışmaları düz işlevler tarafından yapılabilir (ve çoğu zaman iletilir). Ancak bu kazan plakası kodunu doğru almanız önemlidir. Başarısız olursanız, ya operatörünüzün kodu derlenmez ya da kullanıcılarınızın kodu derlenmez ya da kullanıcılarınızın kodu şaşırtıcı şekilde davranır.
Atama operatörü
Görevlendirme hakkında söylenecek çok şey var. Bununla birlikte, çoğu GMan'ın ünlü Kopyala ve Değiştir SSS'sinde zaten söylendi , bu yüzden çoğunu burada atlayacağım, sadece referans için mükemmel atama operatörünü listeleyeceğim:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
Bitshift Operatörleri (Akış G / Ç için kullanılır)
Bit kaydırma operatörleri <<
ve C'den devraldıkları >>
bit manipülasyon fonksiyonları için donanım arayüzünde hala kullanılmalarına rağmen, çoğu uygulamada aşırı yüklenmiş akış giriş ve çıkış operatörleri olarak daha yaygın hale gelmiştir. Bit işleme operatörleri olarak aşırı yüklemeye kılavuzluk için, aşağıdaki İkili Aritmetik Operatörler bölümüne bakın. Nesneniz iostreams ile kullanıldığında kendi özel biçiminizi uygulamak ve ayrıştırma mantığınızı uygulamak için devam edin.
En yaygın olarak aşırı yüklenmiş işleçler arasında akım işleçleri, sözdiziminin üye veya üye olmayan olmaları konusunda hiçbir kısıtlama belirtmediği ikili infix işleçleridir. Sol argümanlarını değiştirdikleri için (akışın durumunu değiştirirler), genel kurallara göre, sol operand tiplerinin üyeleri olarak uygulanmaları gerekir. Bununla birlikte, bunların sol işlenenleri standart kitaplıktan gelen akışlardır ve standart kitaplık tarafından tanımlanan akış çıktı ve giriş işleçlerinin çoğu aslında akış sınıflarının üyeleri olarak tanımlanırken, kendi türleriniz için çıktı ve girdi işlemlerini uyguladığınızda, siz standart kitaplığın akış türlerini değiştiremez. Bu nedenle bu operatörleri üye olmayan işlevler olarak kendi türleriniz için uygulamanız gerekir. İkisinin kanonik biçimleri şunlardır:
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
Gerçekleştirirken operator>>
, akışın durumunun manuel olarak ayarlanması, yalnızca okumanın kendisi başarılı olduğunda gereklidir, ancak sonuç beklenen şey değildir.
İşlev çağrısı operatörü
İşlev nesneleri oluşturmak için kullanılan işlev çağrısı operatörü, aynı zamanda işlevler olarak da bilinir, bir üye işlev olarak tanımlanmalıdır , bu nedenle her zaman this
üye işlevlerin örtük argümanına sahiptir. Bunun dışında, sıfır dahil herhangi bir sayıda ek argüman almak için aşırı yüklenebilir.
İşte sözdizimine bir örnek:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
Kullanım:
foo f;
int a = f("hello");
C ++ standart kitaplığı boyunca, işlev nesneleri her zaman kopyalanır. Bu nedenle, kendi işlev nesnelerinizin kopyalanması ucuz olmalıdır. Bir işlev nesnesinin kesinlikle kopyalanması pahalı olan verileri kullanması gerekiyorsa, bu verileri başka bir yerde depolamak ve işlev nesnesinin ona başvurmasını sağlamak daha iyidir.
Karşılaştırma operatörleri
İkili infix karşılaştırma operatörleri, genel kurallara göre üye olmayan işlevler 1 olarak uygulanmalıdır . Tekli önek olumsuzlaması !
(aynı kurallara göre) bir üye işlevi olarak uygulanmalıdır. (ancak onu aşırı yüklemek genellikle iyi bir fikir değildir.)
Standart kitaplığın algoritmaları (ör. std::sort()
) Ve türleri (ör. std::map
) Her zaman yalnızca operator<
mevcut olmayı bekleyecektir . Bununla birlikte, türünüzdeki kullanıcılar, diğer tüm operatörlerin de mevcut olmasını bekleyeceklerdir , bu nedenle tanımlarsanız operator<
, üçüncü temel operatör aşırı yükleme kuralını izlediğinizden ve diğer tüm boolean karşılaştırma operatörlerini tanımladığınızdan emin olun. Bunları uygulamanın kanonik yolu şudur:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
Burada dikkat edilmesi gereken önemli nokta, bu operatörlerden sadece ikisinin aslında herhangi bir şey yapması, diğerlerinin ise asıl işi yapmak için argümanlarını bu ikisinden birine iletmesidir.
Kalan ikili boole operatörlerini ( ||
, &&
) aşırı yükleme sözdizimi , karşılaştırma operatörlerinin kurallarına uyar. Ancak, öyle çok Bunlar için makul kullanım davayı bulacağını olası 2 .
1 Tüm genel kurallarda olduğu gibi, bazen bunu da çiğnemek için nedenler olabilir. Eğer öyleyse, üye fonksiyonlar için ikili karşılaştırma operatörlerinin sol taraf operandının da olması *this
gerektiğini unutmayın const
. Dolayısıyla, üye işlevi olarak uygulanan bir karşılaştırma operatörünün şu imzaya sahip olması gerekir:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
( const
Sonuna dikkat edin.)
2 Bu unutulmamalıdır ki yerleşik sürümü ||
ve &&
kullanım kısayol semantik. Kullanıcı tanımlı olanları (çünkü yöntem çağrıları için sözdizimsel şeker oldukları için) kısayol anlamını kullanmazlar. Kullanıcı bu operatörlerin kısayol anlamsallığına sahip olmasını bekleyecektir ve kodları buna bağlı olabilir, Bu nedenle bunları ASLA tanımlamamanız şiddetle tavsiye edilir.
Aritmetik operatörler
Tekli aritmetik operatörler
Tekli artırma ve azaltma operatörleri hem önek hem de sonek çeşidinde gelir. Birini diğerinden ayırmak için, sonek değişkenleri ek bir kukla int bağımsız değişkeni alır. Arttırma veya azaltmayı aşırı yüklerseniz, her zaman hem önek hem de sonek sürümlerini uyguladığınızdan emin olun. Arttırmanın kanonik uygulaması şöyledir, azaltma aynı kuralları izler:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
Sonek varyantının önek açısından uygulandığına dikkat edin. Ayrıca postfix'in fazladan bir kopya oluşturduğunu da unutmayın. 2
Tekli eksi ve artı aşırı yükleme çok yaygın değildir ve muhtemelen en iyi şekilde önlenir. Gerekirse, muhtemelen üye işlevleri olarak aşırı yüklenmeleri gerekir.
2 Ayrıca, sonek varyantının daha fazla iş yaptığını ve bu nedenle kullanımının önek varyantına göre daha az verimli olduğunu unutmayın. Bu, genellikle ön ek artışını sonek artışına tercih etmek için iyi bir nedendir. Derleyiciler genellikle yerleşik türler için ek postfix artış çalışmasını optimize edebilirken, kullanıcı tanımlı türler için aynısını yapamayabilirler (bu, bir liste yineleyicisi kadar masum görünen bir şey olabilir). Bunu yapmaya alıştığınızda i++
, yerleşik bir tür olmadığında ++i
bunun yerine yapmayı hatırlamak çok zor hale gelir i
(ayrıca bir türü değiştirirken kodu değiştirmeniz gerekir), bu nedenle her zaman bir alışkanlık edinmek daha iyidir. postfix açıkça gerekmedikçe önek artışını kullanma.
İkili aritmetik operatörler
İkili aritmetik operatörler, üçüncü temel kural operatör aşırı yüklenmesini itaat etmeyi unutmayın: Eğer sağlarsanız +
, aynı zamanda sağlamak +=
sağladığınız takdirde -
, değil omit yok -=
vs. Andrew Koenig bileşik atama olduğunu gözlemleyen ilk olduğu söylenir operatörler, bileşik olmayan meslektaşları için bir temel olarak kullanılabilir. Yani, operatör olduğu +
anlamında uygulanan +=
, -
açısından uygulanmaktadır -=
vb
Bizim pratik kurallarımıza göre +
ve refakatçileri üye +=
olmamalı, sol argümanını değiştiren bileşik atama meslektaşları ( vb.) Üye olmalıdır. Burada için örnek teşkil eden bir kod +=
ve +
; diğer ikili aritmetik operatörler aynı şekilde uygulanmalıdır:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
referans başına sonucunu döndürürken sonucunun operator+
bir kopyasını döndürür. Elbette, bir referansı geri vermek, genellikle bir kopyayı geri vermekten daha etkilidir, ancak bu durumda operator+
, kopyalamanın başka bir yolu yoktur. Eğer yazarken a + b
, sonuç neden olan yeni bir değer, olmasını bekliyoruz operator+
yeni bir değer döndürmek zorundadır. 3 Ayrıca operator+
sol operandını const referansı yerine kopyayla aldığına dikkat edin . Bunun nedeni operator=
, argümanını kopya başına alma nedeniyle aynıdır .
Bit işleme operatörleri ~
&
|
^
<<
>>
, aritmetik operatörlerle aynı şekilde uygulanmalıdır. Bununla birlikte, (aşırı yükleme <<
ve >>
çıkış ve giriş hariç) bunları aşırı yüklemek için çok az makul kullanım durumu vardır.
3 Yine bundan çıkarılacak ders, yani a += b
genel olarak daha verimli a + b
ve mümkünse tercih edilmelidir.
Dizi Aboneliği
Dizi indis operatörü, sınıf üyesi olarak uygulanması gereken bir ikili operatördür. Veri öğelerine bir anahtarla erişime izin veren konteyner benzeri türler için kullanılır. Bunları sağlamanın kanonik biçimi şudur:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
Sınıfınızın kullanıcılarının döndürdüğü veri öğelerini değiştirmesini istemediğiniz sürece operator[]
(bu durumda sabit olmayan değişkeni atlayabilirsiniz), her zaman işlecin her iki çeşidini de sağlamalısınız.
Değer_türü'nün yerleşik bir türe başvurduğu biliniyorsa, operatörün sabit değişkeni const başvurusu yerine bir kopya döndürmelidir:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
İşaretçi Benzeri Türler için Operatörler
Kendi yineleyicilerinizi veya akıllı işaretçilerinizi tanımlamak için, tekli önek ayrıştırma operatörünü *
ve ikili infix işaretçi üye erişim operatörünü aşırı yüklemeniz gerekir ->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
Bunların da hemen hemen her zaman hem const hem de const olmayan bir sürüme ihtiyaç duyacağını unutmayın. İçin ->
operatörün, eğer value_type
ait class
(ya da struct
ya da union
, başka bir) tip operator->()
bir kadar ardışık olarak adlandırılır operator->()
olmayan sınıf türü getirileri bir değer.
Tekli operatör adresi asla aşırı yüklenmemelidir.
Bu soruyuoperator->*()
görmek için . Nadiren kullanılır ve bu nedenle nadiren aşırı yüklenir. Aslında yineleyiciler bile onu aşırı yüklemiyor.
Dönüştürme Operatörlerine Devam Et
C ++ 'da Operatör Aşırı Yüklemesinin Üç Temel Kuralı
C ++ 'da operatör aşırı yüklemesi söz konusu olduğunda, uymanız gereken üç temel kural vardır . Tüm bu tür kurallarda olduğu gibi, gerçekten de istisnalar vardır. Bazen insanlar onlardan saptılar ve sonuç kötü bir kod değildi, ancak bu tür olumlu sapmalar çok az ve çok uzak. En azından gördüğüm bu tür 100 sapmadan 99'u haksızdı. Ancak, 1000 üzerinden 999 da olabilir. Bu nedenle aşağıdaki kurallara uysanız iyi olur.
Bir operatörün anlamı açıkça açık ve tartışmasız olmadığında, aşırı yüklenmemelidir. Bunun yerine, iyi seçilmiş bir ada sahip bir işlev sağlayın.
Temel olarak, operatörlere aşırı yükleme yapmanın ilk ve en önemli kuralı, özünde şunu söylüyor: Yapma . Bu tuhaf görünebilir, çünkü operatörün aşırı yüklenmesi hakkında bilinmesi gereken çok şey var ve bu nedenle birçok makale, kitap bölümü ve diğer metinler tüm bunları ele alıyor. Ancak görünüşte bariz olan bu kanıta rağmen, operatörün aşırı yüklenmesinin uygun olduğu yalnızca şaşırtıcı derecede az durum vardır . Bunun nedeni, operatörün uygulama alanında kullanımı iyi bilinmedikçe ve tartışmasız olmadıkça, bir operatörün uygulamasının ardındaki anlambilimin anlaşılmasının gerçekten zor olmasıdır. Popüler inanışın aksine, bu neredeyse hiç böyle değildir.Daima operatörün iyi bilinen anlamlarına bağlı kalın.
C ++, aşırı yüklenmiş işleçlerin anlambilimine hiçbir sınırlama getirmez. Derleyiciniz+
, sağ işlenenden çıkarmakiçin ikilioperatörüuygulayan kodu memnuniyetle kabul edecektir. Ancak, böyle bir operatörün kullanıcıları ifadesini şüpheli aslaa + b
çıkarmak içina
gelenb
. Elbette bu, uygulama alanındaki operatörün anlamının tartışmasız olduğunu varsayar.Her zaman bir dizi ilgili işlemin tamamını sağlayın.
Operatörler birbirleriyle ve diğer işlemlerle ilişkilidir . Türünüz destekliyorsaa + b
, kullanıcılar da arayabilmeyi bekleyecektira += b
. Önek artışını destekliyorsa++a
, onlar daa++
çalışmayıbeklerler. Kontrol edebilirlersea < b
, kesinlikle kontrol edebilmeyi de bekleyeceklerdira > b
. Türünüzü kopyalayıp inşa edebiliyorlarsa, atamanın da çalışmasını beklerler.
Üye ve Üye Olmayan Arasındaki Karara Devam Edin .
C ++ 'da operatör aşırı yüklemesinin Genel Sözdizimi
C ++ 'da yerleşik türler için işleçlerin anlamını değiştiremezsiniz, işleçler yalnızca kullanıcı tanımlı türler 1 için aşırı yüklenebilir . Yani, işlenenlerden en az biri kullanıcı tanımlı tipte olmalıdır. Diğer aşırı yüklenmiş fonksiyonlarda olduğu gibi, operatörler belirli bir parametre seti için yalnızca bir kez aşırı yüklenebilir.
C ++ 'da tüm operatörler aşırı yüklenemez. Aşırı yüklenemeyen operatörler arasında şunlar vardır: .
::
sizeof
typeid
.*
ve C ++ 'daki tek üçlü operatör,?:
C ++ 'da aşırı yüklenebilen operatörler arasında şunlar yer alır:
- aritmetik operatörler:
+
-
*
/
%
ve+=
-=
*=
/=
%=
(tüm ikili infix);+
-
(tekli önek);++
--
(tekli önek ve sonek) - bit işleme:
&
|
^
<<
>>
ve&=
|=
^=
<<=
>>=
(tüm ikili infix);~
(tekli önek) - boole cebiri:
==
!=
<
>
<=
>=
||
&&
(tüm ikili infix);!
(tekli önek) - hafıza yönetimi:
new
new[]
delete
delete[]
- örtük dönüştürme operatörleri
- miscellany:
=
[]
->
->*
,
(tüm ikili infix);*
&
(tümü tekli önek)()
(işlev çağrısı, n-ary infix)
Ancak, bunların hepsini aşırı yükleyebilmeniz , bunu yapmanız gerektiği anlamına gelmez . Operatör aşırı yüklemesinin temel kurallarına bakın.
C ++ 'da, operatörler özel adlara sahip işlevler biçiminde aşırı yüklenir . Diğer fonksiyonlar gibi aşırı operatörler genellikle olarak ya uygulanabilir sol tarafındaki terimin en Çeşidi üyesi fonksiyonu ya da üye olmayan fonksiyonlar . Bunlardan birini kullanmakta özgür olup olmamanız birkaç kritere bağlıdır. 2 Bir x nesnesine uygulanan tekli bir operatör @
3 , as operator@(x)
veya as olarak çağrılır x.operator@()
. Bir ikili infix operatörü @
, nesnelere uygulanan x
ve y
aynı ya da adlandırılır operator@(x,y)
ya da x.operator@(y)
. 4
Üye olmayan işlevler olarak uygulanan işleçler bazen işlenen türlerinin dostudur.
1 "Kullanıcı tanımlı" terimi biraz yanıltıcı olabilir. C ++, yerleşik türler ve kullanıcı tanımlı türler arasında ayrım yapar. İlkine, örneğin int, char ve double; ikincisi, kullanıcılar tarafından tanımlanmamış olsalar bile, standart kitaplıktakiler de dahil olmak üzere tüm yapı, sınıf, birleşim ve enum türlerine aittir.
2 Bu, bu SSS'nin sonraki bir bölümünde ele alınmaktadır.
3 Ben bir yer tutucu olarak kullanabilirsiniz nedenle C geçerli bir operatör ++ değil.@
4 C ++ 'daki tek üçlü operatör aşırı yüklenemez ve tek n-ary operatörü her zaman bir üye işlevi olarak uygulanmalıdır.
C ++ 'da Operatör Aşırı Yüklemesinin Üç Temel Kuralı ile devam edin .
Üye ve Üye Olmayan arasındaki Karar
İkili operatörler =
(atama), []
(dizi aboneliği), ->
(üye erişimi) ve n-ary ()
(fonksiyon çağrısı) operatörü her zaman üye fonksiyonlar olarak uygulanmalıdır , çünkü dilin sözdizimi bunları gerektirir.
Diğer operatörler üye olarak veya üye olmayanlar olarak uygulanabilir. Bununla birlikte, bazılarının genellikle üye olmayan işlevler olarak uygulanması gerekir, çünkü sol işlenenleri sizin tarafınızdan değiştirilemez. Bunlardan en önemlileri giriş ve çıkış operatörleri <<
ve >>
bunların sol işlenenleri standart kitaplıktan değiştiremeyeceğiniz akış sınıflarıdır.
Bunları üye işlev veya üye olmayan işlev olarak uygulamayı seçmeniz gereken tüm operatörler için karar vermek için aşağıdaki genel kuralları kullanın :
- Bir ise tekli operatör , bir şekilde bunu uygulamaya üye fonksiyonu.
- İkili bir operatör her iki işleneni de eşit olarak ele alırsa (onları değiştirmeden bırakır), bu operatörü üye olmayan bir işlev olarak uygulayın.
- İkili operatör yoksa değil işlenenlerinden hem tedavi eşit (genellikle sol işlenen değişecek), bunu bir hale getirmek için yararlı olabilecek üyesi İşlenen özel kısımlarına erişmek için varsa, sol işlenen en türünün işleyişini.
Elbette, tüm genel kurallarda olduğu gibi, istisnalar da var. Bir türün varsa
enum Month {Jan, Feb, ..., Nov, Dec}
ve bunun için artırma ve azaltma işleçlerini aşırı yüklemek istiyorsanız, bunu bir üye işlevi olarak yapamazsınız, çünkü C ++ 'da enum türlerinin üye işlevleri olamaz. Bu yüzden, onu ücretsiz bir işlev olarak aşırı yüklemelisiniz. Ve operator<()
bir sınıf şablonunun içine yerleştirilmiş bir sınıf şablonu için, sınıf tanımında satır içi bir üye işlevi olarak yapıldığında yazmak ve okumak çok daha kolaydır. Ancak bunlar gerçekten nadir istisnalardır.
(Bununla birlikte, eğer bir istisna yapmak, konusunu unutma const
üye fonksiyonları için, örtülü olur, o işlem gören için -lık this
argüman. Üye olmayan bir fonksiyonu olarak operatör bir olarak en soldaki argüman alacaktı Eğer const
referans , bir üye işlevi ile aynı operatörün referans const
yapmak için sonunda *this
bir const
işarete sahip olması gerekir.)
Ortak operatörler aşırı yüklenmeye devam edin .
Dönüşüm Operatörleri (Kullanıcı Tanımlı Dönüşümler olarak da bilinir)
C ++ 'da, derleyicinin türleriniz ve diğer tanımlı türler arasında dönüşüm yapmasına olanak tanıyan dönüştürme işleçleri, işleçler oluşturabilirsiniz. İki tür dönüştürme operatörü vardır, örtük ve açık olanlar.
Örtük Dönüşüm Operatörleri (C ++ 98 / C ++ 03 ve C ++ 11)
Örtük bir dönüştürme operatörü, derleyicinin kullanıcı tanımlı bir türün değerini örtük olarak başka bir türe dönüştürmesine ( int
ve arasındaki dönüşüm gibi) izin verir long
.
Aşağıdaki, örtük bir dönüştürme operatörüne sahip basit bir sınıftır:
class my_string {
public:
operator const char*() const {return data_;} // This is the conversion operator
private:
const char* data_;
};
Tek bağımsız değişkenli oluşturucular gibi örtük dönüştürme işleçleri, kullanıcı tanımlı dönüştürmelerdir. Derleyiciler, bir çağrıyı aşırı yüklenmiş bir işlevle eşleştirmeye çalışırken bir kullanıcı tanımlı dönüşüm sağlar.
void f(const char*);
my_string str;
f(str); // same as f( str.operator const char*() )
İlk başta bu çok yardımcı görünüyor, ancak bununla ilgili sorun, örtük dönüşümün beklenmediğinde bile devreye girmesidir. Aşağıdaki kodda, void f(const char*)
çünkü my_string()
bir lvalue olmadığı için çağrılacaktır , bu nedenle ilki eşleşmez:
void f(my_string&);
void f(const char*);
f(my_string());
Yeni başlayanlar bunu kolayca yanlış anlar ve deneyimli C ++ programcıları bile bazen şaşırırlar çünkü derleyici şüphelenmedikleri bir aşırı yüklemeyi seçer. Bu sorunlar, açık dönüştürme operatörleri ile hafifletilebilir.
Açık Dönüşüm Operatörleri (C ++ 11)
Örtük dönüştürme işleçlerinin aksine, açık dönüştürme işleçleri sizden beklemediğinizde asla devreye girmez. Aşağıdaki, açık bir dönüştürme operatörüne sahip basit bir sınıftır:
class my_string {
public:
explicit operator const char*() const {return data_;}
private:
const char* data_;
};
Dikkat edin explicit
. Şimdi, örtük dönüştürme işleçlerinden beklenmeyen kodu yürütmeye çalıştığınızda, bir derleyici hatası alırsınız:
prog.cpp: 'int main ()' işlevinde: prog.cpp: 15: 18: hata: 'f (my_string)' çağrısı için eşleşen işlev yok prog.cpp: 15: 18: not: adaylar: prog.cpp: 11: 10: not: void f (my_string &) prog.cpp: 11: 10: not: bağımsız değişken 1 için 'dizgimden' 'dizgim &' e bilinen dönüşüm yok prog.cpp: 12: 10: not: void f (const char *) prog.cpp: 12: 10: not: 'my_string'den' const char * 'değişkenine 1 bağımsız değişken için bilinen dönüşüm yok
Açık atama işlecini çağırmak için static_cast
, bir C-stil ataması veya bir yapıcı stili atama (yani T(value)
) kullanmanız gerekir.
Ancak, bunun bir istisnası vardır: Derleyicinin örtük olarak bool
. Ek olarak, derleyicinin dönüştürdükten sonra başka bir örtük dönüştürme bool
yapmasına izin verilmez (bir derleyicinin bir seferde 2 örtük dönüştürme yapmasına izin verilir, ancak en fazla 1 kullanıcı tanımlı dönüştürme).
Derleyici "geçmiş" yapmayacağından bool
, açık dönüştürme operatörleri artık Safe Bool deyimine olan ihtiyacı ortadan kaldırıyor . Örneğin, C ++ 11'den önceki akıllı işaretçiler, integral türlere dönüştürmeyi önlemek için Safe Bool deyimini kullanıyordu. C ++ 11'de, akıllı işaretçiler bunun yerine açık bir operatör kullanır çünkü derleyicinin, bir türü açıkça bool'a dönüştürdükten sonra örtük olarak bir integral türe dönüştürmesine izin verilmez.
Aşırınewdelete Yüklemeye devam edin ve .
Aşırı yükleme new
vedelete
Not: ile Bu yalnızca fırsatlar sözdizimi aşırı yüklemenew
vedelete
değil ile uygulanması bu tür aşırı operatörlerin. Bence aşırı yükleme semantiğinew ve deletekendi SSS'lerini hak ediyor , operatör aşırı yükleme konusunda asla adalet yapamam.
Temel bilgiler
C ++ 'da, iki şey gibi yeni bir ifadenew T(arg)
yazdığınızda, bu ifade değerlendirildiğinde gerçekleşir: İlk operator new
olarak ham bellek elde etmek için çağrılır ve ardından T
bu ham belleği geçerli bir nesneye dönüştürmek için uygun yapıcısı çağrılır. Aynı şekilde, bir nesneyi sildiğinizde, önce yıkıcısı çağrılır ve ardından bellek geri döndürülür operator delete
.
C ++, bu işlemlerin her ikisini de ayarlamanıza izin verir: bellek yönetimi ve tahsis edilen bellekte nesnenin oluşturulması / yok edilmesi. İkincisi, bir sınıf için kurucular ve yıkıcılar yazarak yapılır. Hafıza yönetimi ince ayarı, kendi operator new
ve operator delete
.
Operatör aşırı yüklemesinin temel kurallarından ilki - bunu yapmayın - özellikle aşırı yükleme new
ve delete
. Bu operatörleri aşırı yüklemenin neredeyse tek nedeni performans sorunları ve bellek kısıtlamalarıdır ve çoğu durumda, kullanılan algoritmalardaki değişiklikler gibi diğer eylemler, bellek yönetimini değiştirmeye çalışmaktan çok daha yüksek bir maliyet / kazanç oranı sağlayacaktır.
C ++ standart kitaplığı, önceden tanımlanmış new
ve delete
işleçlerle birlikte gelir . En önemlileri şunlar:
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void* operator new[](std::size_t) throw(std::bad_alloc);
void operator delete[](void*) throw();
İlk ikisi bir nesne için bellek ayırır / ayırır, son ikisi ise bir dizi nesne içindir. Bunların kendi sürümlerini sağlarsanız, bunlar aşırı yüklenmez, ancak standart kitaplıktakilerin yerini alır.
Aşırı yüklerseniz operator new
, operator delete
hiçbir zaman arama niyetinde olmasanız bile her zaman eşleştirmeyi de aşırı yüklemelisiniz . Bunun nedeni, bir kurucu yeni bir ifadenin değerlendirilmesi sırasında atarsa, çalışma zamanı sisteminin belleği, nesneyi oluşturmak için bellek ayırmak için çağrılanla operator delete
eşleşen belleğe geri döndürmesidir operator new
. Bir eşleşme sağlamazsanız operator delete
varsayılan olan denir ve neredeyse her zaman yanlıştır.
Aşırı yüklerseniz new
ve delete
dizi değişkenlerini de aşırı yüklemeyi düşünmelisiniz.
Yerleştirme new
C ++, yeni ve silme operatörlerinin ek bağımsız değişkenler almasına izin verir.
Sözde yerleşim yeni, belirli bir adreste şunlara iletilen bir nesne oluşturmanıza olanak tanır:
class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{
X* p = new(buffer) X(/*...*/);
// ...
p->~X(); // call destructor
}
Standart kütüphane bunun için yeni ve silme operatörlerinin uygun aşırı yüklemeleriyle birlikte gelir:
void* operator new(std::size_t,void* p) throw(std::bad_alloc);
void operator delete(void* p,void*) throw();
void* operator new[](std::size_t,void* p) throw(std::bad_alloc);
void operator delete[](void* p,void*) throw();
Yukarıda verilen yeni yerleşim için örnek kodda operator delete
, X yapıcısı bir istisna atmadıkça asla çağrılmayacağını unutmayın.
Ayrıca aşırı yükleyebilirsiniz new
ve delete
diğer argümanlarla. Yeni yerleşim için ek bağımsız değişkende olduğu gibi, bu bağımsız değişkenler de anahtar kelimeden sonra parantez içinde listelenir new
. Yalnızca tarihsel nedenlerden ötürü, bu tür varyantlara, argümanları bir nesneyi belirli bir adrese yerleştirmek için olmasa bile, genellikle yeni yerleşim adı verilir.
Sınıfa özel yeni ve sil
Çoğu zaman, bellek yönetiminde ince ayar yapmak isteyeceksiniz çünkü ölçüm, belirli bir sınıfın veya bir grup ilgili sınıfın örneklerinin sıklıkla oluşturulduğunu ve yok edildiğini ve çalışma zamanı sisteminin varsayılan bellek yönetiminin, genel performans, bu özel durumda verimsiz bir şekilde ilgilenir. Bunu iyileştirmek için yeniyi aşırı yükleyebilir ve belirli bir sınıf için silebilirsiniz:
class my_class {
public:
// ...
void* operator new();
void operator delete(void*,std::size_t);
void* operator new[](size_t);
void operator delete[](void*,std::size_t);
// ...
};
Bu nedenle aşırı yüklenir, new ve delete statik üye işlevleri gibi davranır. Nesneler my_class
için std::size_t
argüman her zaman olacaktır sizeof(my_class)
. Bununla birlikte, bu operatörler ayrıca türetilmiş sınıfların dinamik olarak tahsis edilmiş nesneleri için çağrılır , bu durumda bundan daha büyük olabilir.
Global yeni ve sil
Global yeniyi aşırı yüklemek ve silmek için, standart kitaplığın önceden tanımlanmış operatörlerini kendi operatörümüzle değiştirmeniz yeterlidir. Ancak, bunun nadiren yapılması gerekir.
operator<
Diyelim ki sahipsin:
struct Foo
{
int a;
double b;
std::ostream& operator<<(std::ostream& out) const
{
return out << a << " " << b;
}
};
Buna göre, şunları kullanamazsınız:
Foo f = {10, 20.0};
std::cout << f;
Bu yana operator<<
bir üyesi fonksiyonu olarak aşırı Foo
, operatörün LHS a olmalıdır Foo
nesne. Bu, kullanmanız gerekeceği anlamına gelir:
Foo f = {10, 20.0};
f << std::cout
ki bu çok sezgisel değildir.
Üye olmayan bir işlev olarak tanımlarsanız,
struct Foo
{
int a;
double b;
};
std::ostream& operator<<(std::ostream& out, Foo const& f)
{
return out << f.a << " " << f.b;
}
Kullanabileceksiniz:
Foo f = {10, 20.0};
std::cout << f;
ki bu çok sezgiseldir.