Operatör ve aşırı yüklendiğinde bir nesnenin adresini nasıl güvenilir bir şekilde alabilirim?


170

Aşağıdaki programı düşünün:

struct ghost
{
    // ghosts like to pretend that they don't exist
    ghost* operator&() const volatile { return 0; }
};

int main()
{
    ghost clyde;
    ghost* clydes_address = &clyde; // darn; that's not clyde's address :'( 
}

clydeAdresini nasıl alabilirim ?

Her tür nesne için eşit derecede iyi çalışacak bir çözüm arıyorum. Bir C ++ 03 çözümü hoş olurdu, ama ben de C ++ 11 çözümleri ile ilgileniyorum. Mümkünse, uygulamaya özgü davranışlardan kaçınalım.

C ++ 11'in std::addressofişlev şablonunun farkındayım , ancak burada kullanmakla ilgilenmiyorum: Standart Kütüphane uygulayıcının bu işlev şablonunu nasıl uygulayabileceğini anlamak istiyorum.


41
@jalf: Bu strateji kabul edilebilir, ama şimdi kafasına söylenen bireyleri yumrukladığım için, iğrenç kodlarını nasıl çözebilirim? :-)
James McNellis

5
@jalf Uhm, bazen bu operatörü aşırı yüklemeniz ve bir proxy nesnesi döndürmeniz gerekir . Gerçi şimdi bir örnek düşünemiyorum.
Konrad Rudolph

5
@Konrad: Ben de. Buna ihtiyacınız varsa, tasarımınızı yeniden düşünmek için daha iyi bir seçenek olabileceğini öneririm, çünkü bu operatörün aşırı yüklenmesi çok fazla soruna neden olur. :)
jalf

2
@Konrad: Yaklaşık 20 yıllık C ++ programlamasında bir keresinde bu operatörü aşırı yüklemeye çalıştım . Bu yirmi yılın en başındaydı. Oh, ve bunu kullanılabilir hale getiremedim. Sonuç olarak, operatör aşırı yükleme SSS girişi "Operatörün normal adresi asla aşırı yüklenmemelidir" der. Bir dahaki sefere bu operatörü aşırı yüklemek için inandırıcı bir örnek bulabilirseniz ücretsiz bira alacaksınız. (Berlin'den ayrıldığınızı biliyorum, bu yüzden bunu güvenle sunabilirim :))
sbi

5
CComPtr<>ve CComQIPtr<>aşırı yüklenmişoperator&
Simon Richter

Yanıtlar:


102

Güncelleme: C ++ 11'de std::addressofbunun yerine kullanılabilir boost::addressof.


Önce kodu Boost'tan kopyalayalım, derleyici bitler etrafında çalışır:

template<class T>
struct addr_impl_ref
{
  T & v_;

  inline addr_impl_ref( T & v ): v_( v ) {}
  inline operator T& () const { return v_; }

private:
  addr_impl_ref & operator=(const addr_impl_ref &);
};

template<class T>
struct addressof_impl
{
  static inline T * f( T & v, long ) {
    return reinterpret_cast<T*>(
        &const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
  }

  static inline T * f( T * v, int ) { return v; }
};

template<class T>
T * addressof( T & v ) {
  return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 );
}

İşleve referans gönderirsek ne olur ?

Not: addressofişlev göstermek için bir işaretçi ile kullanılamaz

C ++ ' void func();da bildirilirse, funcargüman almayan ve sonuç döndürmeyen bir işleve başvuru olur. Bir fonksiyona yapılan bu referans önemsiz bir şekilde fonksiyona bir göstergeye dönüştürülebilir - şuradan @Konstantin: 13.3.3.2'ye göre her ikisi de T &ve T *fonksiyonlar için ayırt edilemez. Birincisi bir Kimlik dönüşümüdür ve ikincisi de "Tam Eşleşme" derecesine sahip İşlevden İşaretçiye dönüşümdür (13.3.3.1.1 tablo 9).

İşlevine başvurunun geçmesine addr_impl_refseçimi için aşırı çözünürlükte bir belirsizlik vardır, fyapay değişken sayesinde çözülmektedir, 0bir olduğu, intbirinci ve bir terfi edilebilir long(İntegral Dönüştürme).

Böylece sadece işaretçiyi döndürüyoruz.

Bir dönüştürme operatörü ile bir tür geçirirsek ne olur?

Dönüştürme operatörü bir sonuç verirseT* , bir belirsizliğe sahibiz: f(T&,long)ikinci argüman için bir İntegral Tanıtım için gereklidir f(T*,int), dönüşüm operatörü için ise ilk çağrılır (@litb sayesinde)

O zaman addr_impl_ref. C ++ Standart görev devreye girer dönüşüm sekansı en fazla bir kullanıcı tanımlı dönüşümde içerebilir. Türü sararak ve addr_impl_refzaten bir dönüşüm sekansının kullanımını zorlayarak, türle birlikte gelen herhangi bir dönüşüm operatörünü "devre dışı bırakırız".

Böylece f(T&,long)aşırı yük seçilir (ve İntegral Yükselme gerçekleştirilir).

Başka herhangi bir tip için ne olur?

Böylece f(T&,long)aşırı yük seçilir, çünkü tip T*parametreyle eşleşmez .

Not: Borland uyumluluğu ile ilgili dosyadaki açıklamalardan, diziler işaretçilere bozulmaz, ancak referans olarak geçirilir.

Bu aşırı yüklenmede ne olur?

operator&Aşırı yüklenmiş olabileceğinden, türe başvurmaktan kaçınmak istiyoruz .

reinterpret_castBu iş için kullanılabilecek Standart garantiler (@Matteo Italia'nın cevabı: 5.2.10 / 10'a bakınız).

Boost, derleyici uyarılarını önlemek için bazı nitelikler constve volatileniteleyiciler ekler (ve const_castbunları kaldırmak için düzgün bir şekilde a ).

  • Dökme T&içinchar const volatile&
  • Şerit constvevolatile
  • &Adresi almak için operatörü uygulayın
  • Bir T*

const/ volatileHokkabazlık kara büyü biraz, ama (daha doğrusu 4 aşırı yükleri sağlamak yerine) çalışmalarını kolaylaştırmak yapar. Çünkü o Not Tniteliksiz, biz bir geçirirseniz ghost const&, o zaman T*olduğunu ghost const*böylece eleme gerçekten kayıp edilmemiştir.

EDIT: işaretçi aşırı işlevlerini işaretçi için kullanılır, ben yukarıdaki açıklamayı biraz değiştirdim. Yine de neden gerekli olduğunu hala anlamıyorum .

Aşağıdaki ideone çıktısı bunu bir şekilde özetliyor.


2
"Bir işaretçi geçersek ne olur?" bölüm yanlış. Bir tip U'ya bir işaretçi iletirsek, işlevin adresi 'T' türünün 'U *' olduğu ve addr_impl_ref'in iki aşırı yüklenmesi olacaktır: 'f (U * &, uzun)' ve 'f (U **, int) ', açıkça ilk seçenek seçilecektir.
Konstantin Oznobihin

@Konstantin: doğru, ffonksiyon şablonlarının bulunduğu iki aşırı yüklenmenin, bir şablon sınıfının düzenli üye fonksiyonları olduğu, bunu işaret ettiğiniz için teşekkürler. (Şimdi sadece aşırı yükün, herhangi bir
ipucunun

Bu harika, iyi açıklanmış bir cevap. Biraz daha "dökümden" daha fazla olduğunu düşündüm char*. Teşekkürler Matthieu.
James McNellis

@James: Ben bir sopayla bir hata yaptım her zaman kafamı vuracak @Konstantin çok yardım oldu: D
Matthieu M.

3
Neden bir dönüştürme işlevi olan türler üzerinde çalışmak gerekir? Herhangi bir dönüşüm işlevinin çağrılması yerine tam eşleşmeyi tercih etmez mi T*? EDIT: Şimdi anlıyorum. Bu olurdu, ancak 0argümanla bir çaprazlama ile sonuçlanacaktır , bu yüzden belirsiz olacaktır.
Johannes Schaub - litb

99

Kullanın std::addressof.

Bunu perde arkasında aşağıdakileri yapmak olarak düşünebilirsiniz:

  1. Nesneyi karaktere başvuru olarak yeniden yorumlama
  2. Adresini al (aşırı yükü çağırmaz)
  3. İşaretçiyi türünüzün işaretçisi haline getirin.

Mevcut uygulamalar (Boost.Addressof dahil) tam olarak bunu yapar, sadece ek dikkat constve volatilekalifikasyon alır.


16
Kolayca anlaşılabileceği için bu açıklamayı seçilenden daha iyi seviyorum.
Kızak

49

boost::addressofArkadaki hile ve @Luc Danton tarafından sağlanan uygulama reinterpret_cast; standart §5.2.10 ¶10'da

“Pointer to ” türündeki bir ifade a kullanılarak “pointer to ” türüne açıkça dönüştürülebiliyorsa, türdeki bir lvalue ifadesi T1“reference to T2” türüne T1dönüştürülebilir . Yani, bir referans kademesi , yerleşik ve operatörlerle dönüşümle aynı etkiye sahiptir . Sonuç, kaynak lvalue ile aynı nesneyi ifade eden ancak farklı türde bir lvalue'dur.T2reinterpret_castreinterpret_cast<T&>(x)*reinterpret_cast<T*>(&x)&*

Şimdi, bu, rastgele bir nesne referansını char &(referansın cv-nitelikli ise cv yeterliliğiyle) dönüştürmemize izin verir , çünkü herhangi bir işaretçi (muhtemelen cv-nitelikli) dönüştürülebilir char *. Artık bir char &nesneye sahip olduğumuza göre , nesneye aşırı yüklenen &işleç artık alakalı değildir ve adresi yerleşik işleçle alabiliriz .

Boost uygulaması, cv nitelikli nesnelerle çalışmak için birkaç adım ekler: birincisi reinterpret_castyapılır const volatile char &, aksi takdirde düz bir char &döküm işe yaramaz constve / veya volatilereferanslar ( reinterpret_castkaldırılamaz const). Sonra constve volatileile kaldırılır const_cast, adres ile birlikte alınır &ve reinterpet_cast"doğru" tür için bir final yapılır.

Sabit olmayan / uçucu referanslara eklenmiş olabilecek / karakterini const_castkaldırmak için gereklidir , ancak ilk etapta bir / referans olana "zarar vermez" , çünkü final cv kalifikasyonunu tekrar ekleyecektir orada ilk etapta ( kaldıramaz ama ekleyebilir).constvolatileconstvolatilereinterpret_castreinterpret_castconst

Kodun geri kalanına gelince addressof.hpp, çoğu geçici çözüm içindir. static inline T * f( T * v, int )Sadece Borland derleyicisi için gerekli gibi görünüyor, ama onun varlığı için ihtiyaç tanıtır addr_impl_refaksi işaretçi türleri, bu ikinci aşırı yük tarafından yakalanmış olacağını.

Düzenleme : çeşitli aşırı yüklerin farklı bir işlevi vardır, @Matthieu M. mükemmel cevap bakın .

Bundan da emin değilim; Bu kodu daha fazla araştırmalıyım, ama şimdi akşam yemeği pişiriyorum :), daha sonra bir göz atacağım.


Matthieu M. adresinin adrese geçişine ilişkin açıklama yanlıştır. Harika cevabınızı bu tür düzenlemelerle
bozmayın

"iyi iştah", ileri araştırma aşırı yük fonksiyonları için çağrıldığını gösterir void func(); boost::addressof(func);. Ancak aşırı yükü kaldırmak, gcc 4.3.4'ün kodu derlemesini ve aynı çıktıyı üretmesini engellemez, bu yüzden hala bu aşırı yüke sahip olmanın neden gerekli olduğunu anlamıyorum .
Matthieu M.Haz

@ Matthieu: gcc'de bir hata gibi görünüyor. 13.3.3.2'ye göre, hem T & hem de T * fonksiyonlar için ayırt edilemez. Birincisi bir Kimlik dönüşümüdür ve ikincisi de "Tam Eşleşme" derecesine sahip İşlevden İşaretçiye dönüşümdür (13.3.3.1.1 tablo 9). Bu yüzden ek argümanlara sahip olmak gerekiyor.
Konstantin Oznobihin

@Matthieu: Sadece gcc 4.3.4 ( ideone.com/2f34P ) ile denedim ve beklendiği gibi belirsizlik elde etti. Uygulama adresi veya serbest işlev şablonları gibi aşırı yüklenmiş üye işlevlerini denediniz mi? Sonuncusu ( ideone.com/vjCRs gibi ) temüle argüman kesinti kuralları (14.8.2.1/2) nedeniyle 'T *' aşırı yüklenmesinin seçilmesine neden olacaktır.
Konstantin Oznobihin

2
@curiousguy: Neden böyle olması gerektiğini düşünüyorsun? Derleyicinin ne yapması gerektiğine ve erişimime sahip olduğum tüm derleyicilere (gcc 4.3.4, comeau-online, VC6.0-VC2010 dahil ancak bunlarla sınırlı olmamak üzere) reçete eden belirli C ++ standart parçalarına atıfta bulundum. Bu dava ile ilgili gerekçenizi açıklar mısınız?
Konstantin Oznobihin

11

Bunu bir uygulama gördüm addressof:

char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);

Bana bunun ne kadar uygun olduğunu sorma!


5
Yasal. char*, diğer adlandırma kurallarına ilişkin listelenen istisnadır.
Köpek yavrusu

6
@DeadMG Bunun uygun olmadığını söylemiyorum. Bana
sormaman

1
@DeadMG Burada diğer adlandırma sorunu yok. Soru şu: reinterpret_cast<char*>iyi tanımlanmış.
curiousguy

2
@curiousguy ve cevap evet, her zaman herhangi bir işaretçi türünü yayınlayabilir ve [unsigned] char *böylece sivri uçlu nesnenin nesne temsilini okuyabilir. Bu, charözel ayrıcalıklara sahip başka bir alandır .
underscore_d

@underscore_d Bir oyuncu kadrosunun "her zaman izin verilmiş" olması, oyuncu kadrosunun sonucuyla herhangi bir şey yapabileceğiniz anlamına gelmez.
curiousguy

5

Boost :: addressof ve uygulamasına bir göz atın .


1
Boost kodu, ilginç olsa da, tekniğinin nasıl çalıştığını açıklamaz (iki aşırı yüklenmenin neden gerekli olduğunu da açıklamaz).
James McNellis

Şunu mu demek istediniz: 'static inline T * f (T * v, int)' aşırı yük? Görünüşe göre sadece Borland C geçici çözümü için gerekli. Kullanılan yaklaşım oldukça basittir. Orada sadece ince (standart dışı) şey 'T &' char 'char' dönüşüm olduğunu. Standart olmasına rağmen, 'T *' den 'char *' a kadar döküm yapılmasına izin verir, referans döküm için böyle bir gereklilik yoktur. Bununla birlikte, çoğu derleyicide aynı şekilde çalışmasını bekleyebilirsiniz.
Konstantin Oznobihin

@Konstantin: aşırı işaretçi kullanılır çünkü işaretçi addressofiçin işaretçinin kendisini döndürür. Kullanıcının istediği veya istemediği tartışılabilir, ancak nasıl belirtildiği.
Matthieu M.Haz

@Matthieu: emin misin? Anlayabildiğim kadarıyla, herhangi bir tip (işaretçi türleri dahil) bir içine sarılır addr_impl_ref, bu nedenle işaretçi aşırı yüklemesi asla çağrılmamalıdır ...
Matteo Italia

1
@KonstantinOznobihin bu soruya gerçekten cevap vermiyor, tek söylediğiniz tek şey cevabı nerede arayacağınız değil , cevabın ne olduğu .
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.