C ++ 'da ne zaman' arkadaş 'kullanmalısınız?


354

C ++ SSS ile okuyordum ve friendbeyanı merak ettim . Şahsen hiç kullanmadım, ancak dili keşfetmekle ilgileniyorum.

Kullanmaya iyi bir örnek nedir friend?


SSS'yi biraz daha uzun okumak, << >>bu sınıfların arkadaşı olarak aşırı yükleme ve ekleme fikrini seviyorum . Ancak bunun nasıl kapsüllenmeyi bozmadığından emin değilim. Bu istisnalar ne zaman OOP'nin katılığı içinde kalabilir?


5
Bir arkadaş sınıfının mutlaka Kötü Bir Şey olmadığı cevabını kabul etsem de, bunu küçük bir kod olarak kabul etme eğilimindeyim. Her zaman olmasa da, çoğu zaman sınıf hiyerarşisinin yeniden gözden geçirilmesi gerektiğini gösterir.
Mawg, Monica'yı

1
Zaten sıkı bağlantı olan bir arkadaş sınıfı kullanırsınız. Bunun için yapılmış. Örneğin, bir veritabanı tablosu ve dizinleri sıkıca bağlıdır. Bir tablo değiştiğinde, tüm dizinlerinin güncellenmesi gerekir. Bu nedenle, DBIndex sınıfı DBTable'ı bir arkadaş olarak bildirir, böylece DBTable'ın dizin içlerine doğrudan erişebilir. Ancak DBIndex için genel bir arabirim olmazdı; bir dizini okumak bile mantıklı değil.
shawnhcorey

Çok az pratik deneyime sahip OOP "püristleri", arkadaşın OOP ilkelerini ihlal ettiğini savunur, çünkü bir sınıf, özel devletinin tek koruyucusu olmalıdır. İki sınıfın paylaşılan bir özel devleti sürdürmesi gereken ortak bir durumla karşılaşıncaya kadar bu iyi.
kaalus

Yanıtlar:


335

Öncelikle (IMO) friendfaydalı olmadığını söyleyen insanları dinlemeyin . Bu kullanışlı. Çoğu durumda, herkese açık olarak sunulması amaçlanmayan veri veya işlevselliğe sahip nesneler olacaktır. Bu, özellikle farklı alanlara yüzeysel olarak aşina olabilecek birçok yazarlı büyük kod tabanları için geçerlidir.

Arkadaş belirleyicisine alternatifler vardır, ancak genellikle hantal (cpp düzeyinde beton sınıfları / maskelenmiş tip tanımları) veya kusursuz değildir (yorumlar veya işlev adı kuralları).

Cevap üzerine;

friendBelirteci arkadaşı beyanda sınıfı içinde korunan veri veya işlevselliğine belirlenen sınıf erişim sağlar. Örneğin, aşağıdaki kodda, herhangi bir çocuk bir çocuktan adını isteyebilir, ancak sadece anne ve çocuk adı değiştirebilir.

Pencere gibi daha karmaşık bir sınıfı düşünerek bu basit örneği daha ileriye götürebilirsiniz. Büyük olasılıkla bir Pencerede, herkesin erişebileceği, ancak WindowManager gibi ilgili bir sınıfın ihtiyaç duyduğu birçok işlev / veri öğesi bulunur.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

114
Ek bir not olarak, C ++ SSS, kapsüllemeyi friend geliştiren bahseder . Tıpkı üyelere olduğu gibi üyelere seçici erişim izni friendverir . Herhangi bir hassas kontrol, halkın erişimini sağlamaktan daha iyidir. Diğer diller de seçici erişim mekanizmalarını tanımlar, C # 'ları düşünün . Kullanımıyla ilgili en olumsuz eleştiri , genellikle kötü bir şey olarak görülen daha sıkı bağlantı ile ilgilidir. Bununla birlikte, bazı durumlarda, daha sıkı bağlantı tam olarak istediğiniz şeydir ve size bu gücü verir. protectedinternalfriendfriend
André Caron

5
Lütfen (cpp seviyesi beton sınıflar) ve (maskelenmiş tip tanımları) hakkında daha fazla şey söyleyebilir misiniz, Andrew ?
OmarOthman

18
Bu cevap friend, motive edici bir örnek sunmak yerine neyin açıklanmasına daha çok odaklanmış gibi görünüyor . Window / WindowManager örneği, gösterilen örnekten daha iyidir, ancak çok belirsizdir. Bu cevap aynı zamanda sorunun kapsülleme kısmını da ele almamaktadır.
bames53

4
C ++ 'ın tüm üyelerin uygulama ayrıntılarını paylaşabileceği bir paket fikri olmadığı için' arkadaş 'etkili bir şekilde var mı? Gerçek dünyadaki bir örnekle gerçekten ilgilenirim.
weberc2

1
Anne / Çocuk örneğinin talihsiz olduğunu düşünüyorum. Bu adlar örnekler için uygundur, ancak sınıflar için uygun değildir. (Sorun 2 annemiz olup olmadığını ve her birinin kendi çocuğu olduğunu gösteriyor).
Jo So

162

İş yerinde, kodu test etmek için arkadaşlarımızı kapsamlı bir şekilde kullanıyoruz. Bu, ana uygulama kodu için uygun kapsülleme ve bilgi gizleme sağlayabileceğimiz anlamına gelir. Ancak, iç durumu ve test için verileri incelemek için arkadaşları kullanan ayrı bir test koduna da sahip olabiliriz.

Friend anahtar kelimesini tasarımınızın temel bir bileşeni olarak kullanmayacağımı söylemek yeterli.


Ben de tam olarak bunun için kullanıyorum. Bu veya üye değişkenleri korumalı olarak ayarlayın. C ++ / CLI için çalışmayan bir utanç :-(
Jon Cage

12
Şahsen bunu caydırırdım. Genellikle bir arabirimi test edersiniz, yani bir dizi girdi beklenen çıktıları verir. Dahili verileri neden incelemeniz gerekiyor?
Graeme

55
@Graeme: Çünkü iyi bir test planı hem beyaz kutu hem de kara kutu testini içerir.
Ben Voigt

1
Bu cevapta mükemmel açıklandığı gibi @Graeme ile hemfikirim .
Alexis Leclerc

2
@Gerçek doğrudan dahili veri olmayabilir. Bu yöntemde sınıfa özel olan ve diğer bazı nesnelerin kendi sınıfıyla korunan yöntemi beslemek veya tohumlamak zorunda kalırken, bu yöntemde belirli bir işlemi veya görevi gerçekleştiren bir yöntem olabilir.
Francis Cugler

93

friendAnahtar kelime iyi bir dizi kullanıma sahiptir. İşte hemen görülebilen iki kullanım:

Arkadaş Tanımı

Arkadaş tanımı, sınıf kapsamındaki bir işlevi tanımlamaya izin verir, ancak işlev bir üye işlevi olarak değil, çevreleyen ad alanının serbest işlevi olarak tanımlanır ve bağımsız değişkene bağımlı arama dışında normal olarak görünmez. Bu özellikle operatörün aşırı yüklenmesi için faydalıdır:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Özel CRTP Temel Sınıfı

Bazen, bir politikanın türetilmiş sınıfa erişmesi gerektiğini görürsünüz:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

Bu cevapta bunun için tartışmasız bir örnek bulacaksınız . Bunu kullanan başka bir kod bu cevapta. CRTP tabanı, veri-üye-işaretçiler kullanarak türetilmiş sınıfın veri alanlarına erişebilmek için bu işaretçisini verir.


Merhaba, CRTP'nizi denediğimde bir sözdizimi hatası alıyorum (xcode 4'te). Xcode, bir sınıf şablonunu devralmaya çalıştığımı düşünüyor. Hata gerçekleşir P<C>içinde template<template<typename> class P> class C : P<C> {};"sınıfı şablonu C Şablon kullan argümanlar gerektiriyor" belirten. Aynı sorunları yaşadın mı, belki de bir çözüm biliyor musunuz?
bennedich

@bennedich, ilk bakışta yetersiz C ++ özellik desteği ile alacağınız hataya benziyor. Hangi derleyiciler arasında oldukça yaygındır. Kullanımı FlexibleClassdahilinde FlexibleClassolmalıdır örtük kendi türünü gösteriyor.
Yakk - Adam Nevraumont

@bennedich: Sınıf şablonunun adının sınıf gövdesinden kullanım kuralları C ++ 11 ile değişti. Derleyicinizde C ++ 11 modunu etkinleştirmeyi deneyin.
Ben Voigt

Visual Studio 2015'te şu herkesi ekleyin: f () {}; f (int_type t): değer (t) {}; Bu derleyici hatasını önlemek için: hata C2440: '<function-style-cast>': 'utils :: f :: int_type' den 'utils :: f' notuna dönüştürülemiyor Not: Hiçbir kurucu kaynak türünü veya yapıcısını alamadı aşırı yük çözünürlüğü belirsizdi
Damian

-Std = c ++ 11 amd gcc 5.1 ile -std = c ++ 11, -std = c ++ 14 ve -std = c ++ 17 ile gcc 4.8 kullanıldığında, bu hatayla sonuçlanan her zaman: error: type/value mismatch at argument 1 in template parameter list for 'template<class> class SomePolicy' struct FlexibleClass : private SomePolicy<FlexibleClass> ^ crtp.cpp:17:56: note: expected a type, got 'FlexibleClass'Gerçekten mümkün mü? kullanarak templated CRTP bu tür nasıl? ( using some_type = int;başında eklediğimi unutmayın )
Maitre Bart

41

@roo : Kapsülleme burada kopmaz çünkü sınıfın kendisi özel üyelerine kimin erişebileceğini belirler. Kapsülleme ancak bu durum sınıfın dışından kaynaklanıyorsa, örneğin operator <<“sınıfın bir arkadaşıyım” ilan ederseniz kırılabilir foo.

friendcümledeki kullanımı publicdeğil kullanımı, private!

Aslında, C ++ SSS buna zaten cevap veriyor .


14
"arkadaş özel kullanımı değil, kamu kullanımı yerine!", Ben ikinci
Waleed Eissa

26
@Assaf: evet, ama FQA, çoğu zaman, gerçek değeri olmayan çok tutarsız öfkeli anlamsız. Buradaki kısım bir friendistisna değildir. Buradaki tek gerçek gözlem, C ++ 'ın yalnızca derleme zamanında kapsülleme sağlamasıdır. Ve bunu söylemek için başka kelimeye ihtiyacın yok. Gerisi bolloktur. Yani, özet olarak: FQA'nın bu bölümünden bahsetmeye değmez.
Konrad Rudolph

12
FQA çoğu mutlak
blx olduğunu

1
@Konrad: "Buradaki tek gerçek gözlem, C ++ 'ın yalnızca derleme zamanında kapsülleme sağlamasıdır." Herhangi bir dil bunu çalışma zamanında sağlıyor mu? Bildiğim kadarıyla, C #, Java, Python ve diğerlerinde özel üyelere (ve işaretçilerin işlevlere veya birinci sınıf nesneler olarak işlevlere izin veren dillere) başvurular döndürmesine izin verilir.
André Caron

André @: JVM ve CLR aslında edebilirsiniz olarak bildiğim kadarıyla Bunu sağlamak. Her zaman yapılıp yapılmadığını bilmiyorum ama paketleri / montajları böyle bir saldırıya karşı koruyabileceğiniz iddia ediliyor (bunu asla kendim yapmadım).
Konrad Rudolph

27

Kurallı örnek, operatörün << aşırı yüklenmesidir. Bir diğer yaygın kullanım, bir yardımcıya veya yönetici sınıfına dahili cihazlarınıza erişim izni vermektir.

İşte C ++ arkadaşları hakkında duyduğum birkaç kılavuz. Sonuncusu özellikle unutulmaz.

  • Arkadaşlarınız çocuğunuzun arkadaşı değil.
  • Çocuğunuzun arkadaşları sizin arkadaşınız değil.
  • Özel bölümlerinize yalnızca arkadaşlar dokunabilir.

" Kurallı örnek operatörü << aşırı yüklemektir. " friendSanırım kullanmama kanonik .
curiousguy

16

edit: SSS biraz daha uzun okuma << >> operatör aşırı yükleme ve bu sınıfların bir arkadaşı olarak ekleme fikrini seviyorum, ancak bu nasıl kapsülleme kırmaz emin değilim

Kapsüllemeyi nasıl kırabilir?

Bir veri üyesine sınırsız erişime izin verdiğinizde kapsüllemeyi kesersiniz . Aşağıdaki sınıfları düşünün:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1olduğu açıkça saklanmış. Herkes okuyabilir ve içinde değişiklik yapabilir x. Herhangi bir erişim kontrolünü zorlamanın hiçbir yolu yoktur.

c2açık bir şekilde kapsüllenmiştir. Herkese açık erişim yok x. Yapabileceğiniz tek şey , sınıf üzerinde bazı anlamlı işlemlerfoo gerçekleştiren işlevi çağırmaktır .

c3? Bu daha az kapsüllenmiş mi? Sınırsız erişime izin veriyor xmu? Bilinmeyen işlevlerin erişmesine izin veriyor mu?

Hayır. Bir özelliğin sınıfın özel üyelerine erişmesini . Tıpkı c2yaptığı gibi . Ve tıpkı c2erişimi olan bir işlev "bazı rastgele, bilinmeyen işlev" değil, "sınıf tanımında listelenen işlev" dir. Tıpkı c2, sınıf tanımlarına bakarak, kimin erişime sahip olduğunun tam bir listesini görebiliriz.

Peki, bu tam olarak nasıl daha az kapsüllenir? Aynı miktarda kod, sınıfın özel üyelerine erişime sahiptir. Ve erişimi olan herkes sınıf tanımında listelenir.

friendkapsüllemeyi bozmaz. Bazı Java insan programcılarını rahatsız ediyor, çünkü "OOP" derken aslında "Java" demek . "Kapsülleme" dedikleri zaman, "özel üyelerin keyfi erişimlere karşı korunması gerektiği" anlamına gelmez, bunun yerine "tam üyelere erişebilen tek işlevlerin sınıf üyeleri olduğu bir Java sınıfı" anlamına gelir . birkaç sebep .

İlk olarak, daha önce gösterildiği gibi, çok kısıtlayıcı. Arkadaş yöntemlerinin aynı şeyi yapmasına izin verilmemesi için hiçbir neden yoktur.

İkincisi, yeterince kısıtlayıcı değil . Dördüncü sınıfı düşünün:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

Bu, yukarıda bahsedilen Java zihniyetine göre, mükemmel bir şekilde kapsüllenmiştir. Yine de, kesinlikle herkesin x'i okumasına ve değiştirmesine izin verir . Bu nasıl bir anlam ifade ediyor? (ipucu: Öyle değil)

Alt satır: Kapsülleme, hangi işlevlerin özel üyelere erişebileceğini denetleyebilmekle ilgilidir. Bu işlevlerin tanımlarının tam olarak nerede olduğu ile ilgili değildir .


10

Andrew örneğinin bir başka yaygın versiyonu olan korkunç kod-couplet

parent.addChild(child);
child.setParent(parent);

Her iki satır da her zaman birlikte ve tutarlı bir şekilde yapılırsa endişelenmek yerine, yöntemleri özel hale getirebilir ve tutarlılığı uygulamak için bir arkadaş işlevine sahip olabilirsiniz:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

Başka bir deyişle, ortak arayüzleri daha küçük tutabilir ve arkadaş işlevlerinde sınıfları ve nesneleri kesen değişmezleri zorlayabilirsiniz.


6
Bunun için neden bir arkadaşa ihtiyaç var? Neden addChildüye işlevinin üst öğeyi ayarlamasına izin vermiyorsunuz ?
Nawaz

1
setParentMüşterilerin üst öğeyi değiştirmesine izin vermek istemediğinizden, addChild/ removeChildişlev kategorisinde yöneteceğinizden daha iyi bir örnek arkadaş edinmek olabilir .
Ylisar

8

Özel / Korumalı / Genel haklarını kullanarak üyeler ve işlevler için erişim haklarını denetliyor musunuz? bu 3 seviyenin her birinin fikrinin açık olduğunu varsayarsak, o zaman bir şey eksik olduğumuz açık olmalı ...

Örneğin bir üyenin / işlevin korunan olarak bildirilmesi oldukça geneldir. Bu işlevin herkes için erişilemeyeceğini söylüyorsunuz (elbette miras alınan bir çocuk hariç). Peki ya istisnalar? her güvenlik sistemi bir tür "beyaz liste" ye sahip olmanızı sağlar, değil mi?

Böylece arkadaş , kaya gibi katı nesne izolasyonuna sahip olma esnekliğine sahip olmanıza izin verir, ancak haklı olduğunu düşündüğünüz şeyler için bir "boşluk" oluşturulmasına izin verir.

Sanırım insanlar bunun gerekli olmadığını söylüyor çünkü her zaman onsuz yapacak bir tasarım var. Küresel değişkenlerin tartışmasına benzer olduğunu düşünüyorum: Onları asla kullanmamalısınız, Onlarsız yapmanın her zaman bir yolu vardır ... ama gerçekte, bunun (neredeyse) en zarif yol olduğu durumları görüyorsunuz. .. Bence bu arkadaşlarla aynı durum.

Bir ayar işlevini kullanmadan üye değişkenine erişmenize izin vermek dışında gerçekten iyi değil

tam olarak ona bakmanın yolu bu değil. Fikir, DSÖ'nün bir ayar fonksiyonunun onunla ne ilgisi olup olmadığını, ne yapabileceğini kontrol etmektir.


2
friendBir boşluk nasıl ? O sağlayan sınıf listelenen yöntemleri erişim kendi özel üyeler. Hala rastgele kodların onlara erişmesine izin vermiyor. Bu nedenle, bir kamu üyesi işlevinden farklı değildir.
jalf

Arkadaşınız C ++ 'da C # / Java paket düzeyinde erişim alabileceğiniz kadar yakındır. @jalf - arkadaş sınıflarına ne dersin (fabrika sınıfı gibi)?
Ogre Psalm33

1
@Ogre: Peki ya onlar? Hâlâ özel olarak bu sınıfa veriyorsunuz ve hiç kimsenin sınıfın içlerine erişmesine izin vermiyorsunuz . Sadece sınıfınızla ilgili bilinmeyen kod için kapıyı açık bırakmıyorsunuz.
jalf

8

Arkadaş erişimini kullanmak için kullanışlı bir yer buldum: Özel fonksiyonların Unittest'i.


Fakat bunun için bir kamu işlevi de kullanılabilir mi? Arkadaş erişimini kullanmanın avantajı nedir?
Zheng Qu

@Maverobot Sorunuzu biraz açıklayabilir misiniz?
VladimirS

5

Bir kap oluşturduğunuzda ve bu sınıf için bir yineleyici uygulamak istediğinizde arkadaşınız kullanışlı olur.


4

Daha önce çalıştığım bir şirkette ilginç bir sorunla karşılaştık, burada arkadaşımızı iyi etkilemek için kullandık. Çerçeve departmanımızda çalıştım, özel işletim sistemimiz üzerinde temel bir motor seviyesi sistemi oluşturduk. Dahili olarak bir sınıf yapımız vardı:

         Game
        /    \
 TwoPlayer  SinglePlayer

Tüm bu sınıflar çerçevenin bir parçasıydı ve ekibimiz tarafından sürdürülüyordu. Şirket tarafından üretilen oyunlar, Oyun çocuklarından birinden türetilen bu çerçevenin üzerine inşa edilmiştir. Sorun, Game'in SinglePlayer ve TwoPlayer'ın erişmesi gereken çeşitli şeylerle arayüzleri olmasıydı, ancak çerçeve sınıflarının dışında açığa çıkmak istemedik. Çözüm, bu arayüzleri özel yapmak ve TwoPlayer ve SinglePlayer'ın arkadaşlık yoluyla onlara erişmesine izin vermekti.

Doğrusu bu sorun, sistemimizin daha iyi uygulanmasıyla çözülmüş olabilirdi ama sahip olduklarımıza kilitlendik.


4

Kısa cevap şu olurdu: arkadaş gerçekten iyileştiğinde kullanın kapsüllemeyi . Okunabilirliği ve kullanılabilirliği geliştirmek (<< ve >> operatörleri kanonik örnektir) de iyi bir nedendir.

Kapsüllemeyi geliştirmeye yönelik örneklere gelince, diğer sınıfların içleriyle çalışmak için özel olarak tasarlanmış sınıflar (test sınıfları akla gelir) iyi adaylardır.


" << ve >> operatörleri kanonik örnektir. " Hayır. Aksine kanonik karşı örnekler .
curiousguy

@curiousguy: operatörler <<ve >>üyeler yerine genellikle arkadaştırlar, çünkü onları üye yapmak onları garip hale getirir. Tabii ki, bu operatörlerin özel verilere erişmesi gerektiği durumdan bahsediyorum; aksi halde dostluk işe yaramaz.
Gorpik

" Çünkü onları üye yapmak onları kullanmak için garip hale getirecektir. " Açıkçası, üye olmayanlar ya da üyeleri yerine değer sınıfının yapılması operator<<ve operator>>üyeleri i|ostream, istenen sözdizimini sağlamazdı ve ben bunu önermiyorum. " Bu operatörlerin özel verilere erişmesi gerektiği durumdan bahsediyorum " Giriş / çıkış operatörlerinin neden özel üyelere erişmesi gerektiğini tam olarak anlamıyorum.
curiousguy

4

C ++ yaratıcısı, herhangi bir kapsülleme ilkesini bozmadığını söylüyor ve ona alıntı yapacağım:

"Arkadaş" kapsüllemeyi ihlal ediyor mu? Hayır. Olmaz. "Arkadaş", tıpkı üyelik gibi erişim vermek için açık bir mekanizmadır. (Standart bir uyumlu programda) kaynağını değiştirmeden kendinize bir sınıfa erişim izni veremezsiniz.

Daha net ...


@curiousguy: Şablonlarda bile doğrudur.
Nawaz

@Nawaz Şablon dostluğu verilebilir, ancak herkes arkadaşlık verme sınıfını değiştirmeden yeni bir kısmi veya açık uzmanlaşma yapabilir. Ancak bunu yaparken ODR ihlallerine dikkat edin. Ve bunu yine de yapma.
curiousguy

3

Başka bir kullanım: arkadaş (+ sanal miras) bir sınıftan türetmeyi önlemek için kullanılabilir (aka: "bir sınıfı devredilemez hale getir") => 1 , 2

Gönderen 2 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 

3

TDD'yi birçok kez yapmak için C ++ 'arkadaş' anahtar kelimesini kullandım.

Bir arkadaşım benim hakkımda her şeyi bilebilir mi?


Güncellendi: Bjarne Stroustrup sitesinden "arkadaş" anahtar kelimesiyle ilgili bu değerli cevabı buldum .

"Arkadaş", tıpkı üyelik gibi erişim vermek için açık bir mekanizmadır.


3

friendAnahtar kelimeyi ne zaman / nerede kullandığınıza çok dikkat etmelisiniz ve sizin gibi ben de çok nadiren kullandım. Aşağıda kullanım friendve alternatifler hakkında bazı notlar verilmiştir .

Diyelim ki eşit olup olmadığını görmek için iki nesneyi karşılaştırmak istiyorsunuz. Şunlardan birini yapabilirsiniz:

  • Karşılaştırma yapmak için erişimci yöntemlerini kullanın (her bir ivar'ı kontrol edin ve eşitliği belirleyin).
  • Veya tüm üyelere herkese açık olarak doğrudan erişebilirsiniz.

İlk seçenekle ilgili sorun, bunun doğrudan değişken erişimden (biraz) daha yavaş, okunması daha zor ve hantal bir erişimci LOT olabilmesidir. İkinci yaklaşımla ilgili sorun, kapsüllemeyi tamamen kırmanızdır.

Güzel olurdu, bir sınıfın özel üyelerine hala erişebilecek harici bir işlev tanımlayabilirsek. Bunu şu friendanahtar kelime ile yapabiliriz :

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

Yöntem equal(Beer, Beer)artık doğrudan erişimi vardır ave bolabilir 'ın özel üye ( char *brand, float percentAlcoholvb Bu, er oldukça yapmacık örnek geçerli olacak olan friendbir aşırı yüklenme için== operator , ama biz bu alırsınız.

Unutulmaması gereken birkaç nokta:

  • bir friend , sınıfın üye işlevi DEĞİLDİR
  • Sınıfın özel üyelerine özel erişimi olan sıradan bir işlevdir
  • Tüm erişimcileri ve mutasyonları arkadaşlarınızla değiştirmeyin (her şeyi de yapabilirsiniz public!)
  • Arkadaşlık karşılıklı değil
  • Arkadaşlık geçişli değil
  • Arkadaşlık miras alınmaz
  • Veya C ++ SSS'nin açıkladığı gibi : "Sana arkadaşlık erişimi verdiğim için çocuklarının bana otomatik olarak erişmesine izin vermiyor, arkadaşlarının bana otomatik olarak erişmesine izin vermiyor ve otomatik olarak sana erişim izni vermiyor ."

Sadece friendsbunu başka şekilde yapmak çok zor olduğunda kullanıyorum . Başka bir örnek olarak, birçok vektör matematik fonksiyonlar sıklıkla olarak oluşturulan friendskarşılıklı işlerliği nedeniyle Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, vb Ve bu arkadaş olmak yerine, her yerde erişenleri kullanmak zorunda sadece çok kolay. Belirtildiği gibi friend, <<(hata ayıklama için gerçekten kullanışlı) >>ve belki de ==operatöre uygulandığında genellikle yararlıdır , ancak bunun gibi bir şey için de kullanılabilir:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Dediğim gibi, friendçok sık kullanmıyorum , ama şimdi ve sonra sadece ihtiyacınız olan şey. Bu yardımcı olur umarım!


2

Operatör << ve operatör >> ile ilgili olarak, bu operatörlerin arkadaş olması için iyi bir neden yoktur. Üye işlevleri olmaması gerektiği doğrudur, ancak arkadaş olmaları da gerekmez.

Yapılacak en iyi şey genel baskı (ostream &) ve okuma (istream &) fonksiyonlarını oluşturmaktır. Ardından, bu işlevler için işleci << ve işleci >> yazın. Bu, sanal serileştirme sağlayan bu işlevleri sanal yapmanıza izin vermenin ek avantajını sağlar.


" Operatör << ve operatör >> ile ilgili olarak bu operatörlerin arkadaş olması için iyi bir neden yoktur. " Kesinlikle doğru. " Bu, bu işlevleri sanal hale getirmenize ek bir fayda sağlar, " Söz konusu sınıf türetme amaçlıysa, evet. Aksi halde neden rahatsız oluyorsun?
curiousguy

Bu cevabın neden iki kez reddedildiğini ve hiçbir açıklama yapmadan gerçekten anlamıyorum! Bu kaba.
curiousguy

sanal serileştirmede oldukça büyük olabilen mükemmel bir hit ekleyecek
paulm

2

Ben sadece korunan fonksiyonları unittest için friend-anahtar kelime kullanıyorum. Bazıları korumalı işlevselliği test etmemeniz gerektiğini söyleyecektir. Bununla birlikte, yeni işlevsellik eklerken bu çok kullanışlı aracı buluyorum.

Ancak, doğrudan sınıf bildirimlerinde anahtar sözcüğü kullanmıyorum, bunun yerine bunu elde etmek için şık bir şablon kesmek kullanıyorum:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Bu, aşağıdakileri yapmamı sağlıyor:

friendMe(this, someClassInstance).someProtectedFunction();

En az GCC ve MSVC üzerinde çalışır.


2

C ++ "friend" anahtar kelimesinde Operatör aşırı yükleme ve Köprü Yapımı yararlıdır.

1.) Operatör aşırı yüklemesinde arkadaş anahtar kelimesi: Operatör aşırı yüklemesi
için örnek:
"x" (x-koordinat için) ve "y" (y-koordinat için) iki kayan değişkenli bir "Nokta" sınıfımız olduğunu varsayalım . Şimdi aşırı yüklemeliyiz "<<"(ekstraksiyon operatörü),"cout << pointobj" x ve y koordinatlarını (pointobj sınıf noktası nesnesidir) yazdıracaktır. Bunu yapmak için iki seçeneğimiz var:

   1. "ostream" sınıfında aşırı yük "operator << ()" fonksiyonu.
   2. "Nokta" sınıfında aşırı yük "operatörü << ()" fonksiyonu.
Şimdi ilk seçenek iyi değil çünkü eğer bu operatörü başka bir sınıf için tekrar aşırı yüklememiz gerekiyorsa, yine "ostream" sınıfında değişiklik yapmak zorundayız.
Bu yüzden ikinci en iyi seçenektir. Şimdi derleyici "operator <<()"işlevi çağırabilir :

   1. ostream nesnesi cout.As kullanarak: cout.operator << (Pointobj) (ostream sınıfını oluşturur). 
2. bir nesne olmadan arayın. As: operatör << (cout, Pointobj) (Point sınıfından).

Çünkü Point sınıfında aşırı yük uyguladık. Bu işlevi nesne olmadan çağırmak için "friend"anahtar sözcük eklemeliyiz çünkü nesne olmadan bir arkadaş işlevi çağırabiliriz. Şimdi fonksiyon bildirimi şöyle olacaktır:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Köprü yapımında arkadaş anahtar kelimesi:
İki veya daha fazla sınıfın (genellikle "köprü" olarak adlandırılır) özel üyesine erişmemiz gereken bir işlev yapmamız gerektiğini varsayalım. Nasıl yapılır:
Bir sınıfın özel üyesine erişmek için o sınıfın üyesi olmalıdır. Şimdi diğer sınıfların özel üyelerine erişmek için her sınıfın bu işlevi bir arkadaş işlevi olarak ilan etmesi gerekir. Örneğin: İki A ve B sınıfı olduğunu varsayalım. Bir fonksiyon her iki sınıfın da "funcBridge()"özel üyesine erişmek istiyor. O zaman her iki sınıf da şöyle beyan etmelidir "funcBridge()":
friend return_type funcBridge(A &a_obj, B & b_obj);

Bunun arkadaş anahtar kelimesini anlamaya yardımcı olacağını düşünüyorum.


2

Arkadaş beyanı referansının dediği gibi:

Arkadaş bildirimi bir sınıf gövdesinde görünür ve arkadaş bildiriminin göründüğü sınıfın özel ve korunan üyelerine bir işlev veya başka bir sınıf erişimi verir .

Bu nedenle, bir hatırlatma olarak, bazı cevaplarda friendyalnızca korunan üyeleri ziyaret edebileceğini söyleyen teknik hatalar vardır .


1

Ağaç örneği oldukça iyi bir örnektir: Bir miras ilişkisi olmadan birkaç farklı sınıfa uygulanan bir nesneye sahip olmak.

Belki de bir yapıcıya sahip olmanız ve insanları "arkadaş" fabrikanızı kullanmaya zorlamanız gerekebilir.

... Tamam, açıkçası onsuz yaşayabilirsiniz.


1

TDD'yi birçok kez yapmak için '++' anahtar kelimesini C ++ 'da kullandım.
Bir arkadaşım benim hakkımda her şeyi bilebilir mi?

Hayır, bu sadece tek yönlü bir arkadaşlık: ``


1

Kullandığım belirli bir örnek Singleton sınıfları friendoluştururken . Anahtar kelime bana hep sınıfın bir "GetInstance ()" yöntemi olmasından daha özlü bir erişimci işlevi, oluşturmanızı sağlar.friend

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

Bu bir zevk meselesi olabilir, ancak birkaç tuşa basarak tasarruf etmenin burada arkadaşın kullanımını haklı kıldığını düşünmüyorum. GetMySingleton (), sınıfın statik bir yöntemi olmalıdır.
Gorpik

Özel c-tor, MySingleton'u başlatmak için arkadaş olmayan bir işleve izin vermeyecektir, bu nedenle arkadaş anahtar sözcüğü burada gereklidir.
JBRWilkinson

@Gorpik " Bu bir zevk meselesi olabilir, ancak birkaç tuşa basarak tasarruf etmenin burada arkadaşın kullanımını haklı kıldığını düşünmüyorum. " Neyse, friendyok değil belirli bir "gerekçe" ihtiyaç üye işlev eklerken değil.
curiousguy

Singletons zaten kötü bir uygulama olarak kabul edilir (Google "singleton zararlı" ve bunun gibi birçok sonuç elde edersiniz . Bir
anti

1

Arkadaş işlevleri ve sınıfları, genel durumda kapsüllemeyi kırmamak için özel ve korunan sınıf üyelerine doğrudan erişim sağlar. Çoğu kullanım ostream ile: yazabilmek istiyoruz:

Point p;
cout << p;

Bununla birlikte, bu, Point'in özel verilerine erişim gerektirebilir, bu nedenle aşırı yüklenmiş operatörü tanımlarız

friend ostream& operator<<(ostream& output, const Point& p);

Bununla birlikte, açık bir kapsülleme sonuçları vardır. Birincisi, artık arkadaş sınıfı veya işlevi, sınıfın TÜM üyelerine, hatta ihtiyaçları ile ilgili olmayanlara bile tam erişime sahiptir. İkincisi, sınıfın ve arkadaşın uygulamaları şimdi sınıfta içsel bir değişimin arkadaşı kırabileceği noktaya gizleniyor.

Arkadaşınızı sınıfın bir uzantısı olarak görürseniz, bu mantıklı bir şekilde, bir sorun değildir. Ancak, bu durumda, ilk etapta arkadaşı dışarı atmak neden gerekliydi.

'Arkadaşların' elde ettiği iddia edilenle aynı şeyi başarmak için, ancak kapsüllemeyi kırmadan, bunu yapabiliriz:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

Kapsülleme bozuk değildir, B sınıfının A'daki dahili uygulamaya erişimi yoktur, ancak sonuç B'yi A'nın bir arkadaşı olarak ilan etmiş olmamızla aynıdır. Derleyici işlev çağrılarını optimize eder, böylece bu aynı sonuç verir. doğrudan erişim olarak talimatlar.

Bence 'arkadaş' kullanmak tartışmalı faydaya sahip bir kısayoldur, fakat kesin bir maliyettir.


1

Bu gerçek bir kullanım durumu durumu olmayabilir, ancak sınıflar arasında arkadaşın kullanımını göstermeye yardımcı olabilir.

ClubHouse

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

Üye Sınıfı

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Kolaylıklar

class Amenity{};

Burada bu sınıfların ilişkisine bakarsanız; ClubHouse çeşitli türde üyeliklere ve üyelik erişimine sahiptir. Üyelerin tümü bir ortak veya numaralandırılmış bir türü paylaştığı için ortak veya dış sınıfların temel sınıfta bulunan erişim işlevleriyle kimlikleri ve türlerine erişebilmeleri nedeniyle bir süper veya temel sınıftan türetilir.

Ancak, Üyelerin ve Türetilmiş sınıflarının bu tür hiyerarşisi ve ClubHouse sınıfıyla ilişkileri yoluyla, "özel ayrıcalıklara" sahip olan türetilmiş sınıflardan yalnızca biri VIPMember sınıfıdır. Temel sınıf ve türetilmiş diğer 2 sınıf, ClubHouse'un joinVIPEvent () yöntemine erişemez, ancak VIP Üyesi sınıfı, o etkinliğe tam erişime sahipmiş gibi bu ayrıcalığa sahiptir.

VIPMember ve ClubHouse ile, diğer Üye Sınıfların sınırlı olduğu iki yönlü bir erişim caddesi.


0

Sınıf için ağaç algoritmaları uygularken, prof'in bize verdiği çerçeve kodu, düğüm sınıfının bir arkadaşı olarak ağaç sınıfına sahipti.

Bir ayar işlevini kullanmadan üye değişkenine erişmenize izin vermek dışında gerçekten iyi olmaz.


0

Farklı sınıflar (diğerinden miras alınmayan) diğer sınıfın özel veya korunan üyelerini kullanıyorsa arkadaşlığı kullanabilirsiniz.

Arkadaş işlevlerinin tipik kullanım durumları, her ikisinin de özel veya korunan üyelerine erişen iki farklı sınıf arasında gerçekleştirilen işlemlerdir.

dan http://www.cplusplus.com/doc/tutorial/inheritance/ .

Üye olmayan yöntemin bir sınıfın özel üyelerine eriştiği bu örneği görebilirsiniz. Bu yöntem, bu sınıfta sınıfın bir arkadaşı olarak ilan edilmelidir.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

0

Muhtemelen yukarıdaki cevaplardan bir şey kaçırdım ama kapsüllemede önemli bir kavram da uygulamanın gizlenmesidir. Özel veri üyelerine (bir sınıfın uygulama ayrıntıları) erişimi azaltmak, kodun daha sonra daha kolay değiştirilmesini sağlar. Bir arkadaşınız özel verilere doğrudan erişirse, uygulama veri alanlarında (özel veriler) yapılan değişiklikler söz konusu verilere erişen kodu kırın. Erişim yöntemlerini kullanmak çoğunlukla bunu ortadan kaldırır. Bence oldukça önemli.


-1

Sen olabilir sıkı ve en saf cepten ilkelerine bağlı ve herhangi bir sınıf için hiçbir veri üyeleri bile sahip olmasını sağlamak erişimcilere tüm nesneler, böylece gerekir dolaylı geçer onlara hareket etmenin tek yolu olan veriler hakkında bilebilir tek onlar mesajlar , yani yöntemler.

Ancak C # bile dahili bir görünürlük anahtar kelimesine sahiptir ve Java bazı şeyler için varsayılan paket düzeyinde erişilebilirliğe sahiptir . C ++ belirterek aslında yakın cepten ideale bir sınıfa görünürlük uzlaşma minimizinbg tarafından geliyor tam olarak diğer sınıf ve hangi sadece diğer sınıflar içine görebiliyordu.

Gerçekten C ++ kullanmıyorum ama C # arkadaş olsaydı, aslında çok kullandığım derleme-global değiştirici yerine olurdu . .NET dağıtım ünitesi, çünkü gerçekten incapsulation sonu yok olan bir montaj.

Ancak, bir çapraz montaj arkadaş mekanizması gibi davranan InternalsVisibleTo Attribute (otherAssembly) var . Microsoft bunu görsel tasarımcı montajları için kullanır .


-1

Arkadaşlar geri aramalar için de yararlıdır. Geri çağrıları statik yöntemler olarak uygulayabilirsiniz

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

nerede callbackçağırır localCallbackiçten ve clientDataonun içine örneği vardır. Bence,

veya...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

Bunun izin verdiği şey, arkadaşın sadece cpp'de c tarzı bir işlev olarak tanımlanmış olması ve sınıfı karıştırmamasıdır.

Benzer şekilde, çok sık gördüğüm bir desen tüm koymaktır gerçekten , başlığında bildirilmiş cpp'de tanımlanan ve friended başka sınıfa, bir sınıfın özel üyeleri. Bu, kodlayıcının, sınıfın karmaşıklığını ve dahili çalışmasını, başlık kullanıcısından gizlemesini sağlar.

Başlıkta:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

Cpp'de,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

Akış yönünün bu şekilde görmesi gerekmeyen şeyleri gizlemek daha kolay hale gelir.


1
Arayüzler bunu başarmanın daha temiz bir yolu olmaz mıydı? Birinin MyFooPrivate.h dosyasını aramasını durdurmak için ne yapmalıyım?
JBRWilkinson

1
Sırları saklamak için özel ve herkese açık kullanıyorsanız, kolayca yenilebilirsiniz. "Gizlemek" derken, MyFoo kullanıcısının özel üyeleri görmesi gerekmiyor. Bunun yanı sıra, ABI uyumluluğunu korumak yararlıdır. _Private öğesini bir işaretçi yaparsanız, özel uygulama genel arabirime dokunmadan istediğiniz kadar değişebilir ve böylece ABI uyumluluğunu korur.
shash

PIMPL deyiminden bahsediyorsunuz; sizin için ek kapsülleme olmayan bir amaç gibi görünüyor, ancak uygulama ayrıntılarını başlıktan çıkarmak için, bir uygulama ayrıntısının değiştirilmesi istemci kodunun yeniden derlenmesini zorlamaz. Dahası, bu deyimi uygulamak için arkadaş kullanmaya gerek yoktur.
weberc2

İyi evet. Temel amacı uygulama ayrıntılarını taşımaktır. Oradaki arkadaş, kamu sınıfındaki özel üyeleri özel veya başka bir yoldan ele almak için yararlıdır.
15'te şşş
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.