halka açık arkadaş takas üye işlevi


169

Kopyala ve takas deyim güzel cevap biraz yardıma ihtiyacım var kod parçası:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

ve bir not ekliyor

Türümüz için std :: swap konusunda uzmanlaşmamız, serbest işlevli bir takas vb. Boyunca sınıf içi bir takas sağlamamız gerektiği gibi başka iddialar da vardır. ve işlevimiz ADL aracılığıyla bulunur. Bir işlev yapacak.

İle friendben "düşmanca" şartlarda biraz, ben itiraf etmeliyim. Yani, ana sorularım:

  • serbest bir işleve benziyor , ama sınıf gövdesi içinde mi?
  • bu neden swapstatik değil ? Açıkçası üye değişkenleri kullanmıyor.
  • "Herhangi bir takas kullanımı ADL üzerinden takas bulacaktır" ? ADL isim alanlarında arama yapacak, değil mi? Ama aynı zamanda sınıfların içine de bakıyor mu? Yoksa buraya nereden friendgeliyor?

Yan sorular:

  • C ++ 11 ile, benim swapile işaretlemek gerekir noexcept?
  • C ++ 11 ve onun sayesinde aralık-için , ben yerleştirmelidir friend iter begin()ve friend iter end()sınıf içinde aynı şekilde? Sanırım friendburada gerekli değil, değil mi?

Menzil tabanlı ile ilgili yan soruyu göz önünde bulundurarak: üye işlevlerini yazmak ve aralık erişimini std ad alanında (§24.6.5) start () ve end () 'de bırakmak daha iyi bir fikirdir, bunları global veya std ad alanı (bkz. §6.5.4). Ancak, bu işlevlerin <iterator> başlığının bir parçası olması dezavantajı vardır, eğer eklemezseniz, bunları kendiniz yazmak isteyebilirsiniz.
Vitus

2
neden statik değil - çünkü bir friendişlev hiçbir üye işlevi değildir.
aschepler

Yanıtlar:


175

Yazmanın birkaç yolu vardır swap, bazıları diğerlerinden daha iyidir. Zamanla, tek bir tanımın en iyi sonucu verdiği bulundu. Bir swapişlev yazma hakkında nasıl düşünebileceğimizi düşünelim .


Öncelikle aşağıdaki gibi kapların std::vector<>tek bağımsız değişken üye işlevine sahip olduğunu görüyoruz swap:

struct vector
{
    void swap(vector&) { /* swap members */ }
};

Doğal olarak, sınıfımız da öyle olmalı, değil mi? Pek değil. Standart kütüphane her türlü gereksiz şeye sahiptir ve üye swapbunlardan biridir. Neden? Hadi devam edelim.


Yapmamız gereken kanonik ne tanımlamak ve bizim sınıf neyi ihtiyacı onunla çalışmak için yapmak. Ve kanonik takas yöntemi ile std::swap. Bu nedenle üye işlevleri yararlı değildir: genel olarak bir şeyleri nasıl değiştirmemiz gerektiği ve davranışları üzerinde hiçbir etkisi olmaması gerekir std::swap.

Öyleyse, std::swapiş yapmak std::vector<>için uzmanlaşma sağlamalıyız (ve sağlamalıydık) std::swap, değil mi?

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

Bu kesinlikle bu durumda işe yarar, ancak göze batan bir sorunu var: fonksiyon uzmanlıkları kısmi olamaz. Yani, şablon sınıflarını bununla, sadece belirli örneklerle uzmanlaştıramayız:

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

Bu yöntem çoğu zaman çalışır, ancak her zaman işe yaramaz. Daha iyi bir yol olmalı.


Var! Bir friendişlevi kullanabilir ve ADL aracılığıyla bulabiliriz :

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

Bir şeyi değiştirmek istediğimizde, ilişkilendiririz std::swapve ardından niteliksiz bir çağrı yaparız:

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

Bir nedir friendişlevi? Bu alanda karışıklık var.

C ++ standartlaştırılmadan önce, friendfonksiyonlar "arkadaş adı enjeksiyonu" olarak adlandırılan bir şey yaptı, burada kod sanki fonksiyonun etrafındaki isim alanında yazılmış gibi davranıyordu . Örneğin, bunlar eşdeğer ön standarttı:

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

Ancak, ADL icat edildiğinde bu kaldırıldı. Bu durumda friendişlev yalnızca ADL aracılığıyla bulunabilir; Ücretsiz bir fonksiyonu olarak isterse, buna (böylece ilan edilmesi için gerekli görüyoruz örneğin). Ama lo! Bir problem vardı.

Sadece kullanırsanız std::swap(x, y), aşırı yükünüz asla bulunmayacaktır, çünkü açıkça "içeri bakın stdve başka hiçbir yerde" demiştiniz ! Bu yüzden bazı insanlar iki işlev yazmayı önerdi: biri ADL aracılığıyla bulunacak bir işlev olarak , diğeri ise açık std::nitelikleri ele almak için .

Ama gördüğümüz gibi, bu her durumda işe yaramaz ve sonunda çirkin bir karmaşa ile sonuçlanır. Bunun yerine, deyimsel takas diğer rotayı std::swapizledi : sınıfın sağladığı işi yapmak yerine swap, yukarıdaki gibi nitelikli kullanmadığından emin olmak, swapçıların işi . Ve insanlar bunu bildiği sürece, bu oldukça iyi çalışma eğilimindedir. Ama sorun burada yatıyor: kalifiye olmayan bir çağrı kullanmanız istenmiyor!

Bunu kolaylaştırmak için, Boost gibi bazı kütüphaneler işlevini temin boost::swapsadece için tamamlanmamış bir arama yapar swapile std::swapilişkili ad olarak. Bu, şeylerin tekrar özlü olmasına yardımcı olur, ancak yine de bir serseri.

std::swapBen ve diğerleri yanlışlıkla böyle olacağını düşündüm davranışında C ++ 11 değişiklik olmadığını unutmayın . Eğer bunu bitirdiyseniz, burada okuyun .


Kısacası: üye işlevi sadece gürültüdür, uzmanlık çirkin ve eksiktir, ancak friendişlev tamamlanmıştır ve çalışır. Ve takas yaptığınızda ya kullanın boost::swapya swapda std::swapilişkili olmayan bir niteliksiz .


† Gayri resmi olarak, bir işlev çağrısı sırasında dikkate alınacak bir ad ilişkilendirilir . Ayrıntılar için §3.4.2'yi okuyun. Bu durumda, std::swapnormalde dikkate alınmaz; ancak ilişkilendirebiliriz (niteliksiz olarak kabul edilen aşırı yükler kümesine ekleyebiliriz swap) ve bulunmasına izin verebiliriz.


10
Üye işlevinin sadece gürültü olduğuna katılmıyorum. Bir üye işlevi, örneğin std::vector<std::string>().swap(someVecWithData);, swapher iki bağımsız değişken de const olmayan başvuru tarafından iletildiğinden, serbest işlevle mümkün olmayanlara izin verir .
ildjarn

3
@ildjarn: İki satırda yapabilirsiniz. Üye işlevinin olması KURU ilkesini ihlal eder.
GManNickG

4
@GMan: DRY ilkesi, biri diğeri döneminde uygulanırsa geçerli değildir. Aksi halde kimse uygulamalarına sahip bir sınıf savunacağını operator=, operator+ve operator+=simetri için var beklenmesine karşın açıkça alakalı sınıflar üzerinde bu operatörler / kabul edilmektedir. Bence üye swap+ ad alanı kapsamı için de aynı şey geçerli swap.
ildjarn

3
@ Bence çok fazla fonksiyon düşünüyor. Az bilinen, ancak bu bile bir function<void(A*)> f; if(!f) { }nedeni sadece başarısız olabilir Abir beyan operator!kabul ettiğini feşit sıra fkişinin kendi operator!(olası, ancak olabilir). Eğer function<>ölümcül olurdu ' 'operatör bool operatörü 'neden uygulamalıdır '!'? O KURU ihlal eder! S yazarı düşünce ohh ben var''. Bir operator!uygulamanızın olması Ave Aa için bir yapıcıya sahip olmanız gerekir function<...>ve işler bozulur, çünkü her iki aday da kullanıcı tanımlı dönüşümler gerektirir.
Johannes Schaub - litb

1
Bir [üye] takas işlevi yazma hakkında nasıl düşünebileceğimizi düşünelim. Doğal olarak, sınıfımız da öyle olmalı, değil mi? Pek değil. Standart kütüphane her türlü gereksiz şeye sahiptir ve üye takası bunlardan biridir. Bağlı GotW üye takas işlevini savunur.
Xeverous

7

Bu kod ( neredeyse her şekilde) şuna eşdeğerdir :

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

Bir sınıf içinde tanımlanan bir arkadaş işlevi:

  • çevreleyen ad alanına yerleştirilir
  • otomatik olarak inline
  • Daha fazla kalifikasyon olmadan sınıfın statik üyelerine başvurabilir

Tam kurallar bölümünde [class.friend](C ++ 0x taslağının 6. ve 7. paragraflarını alıntılarım):

Bir işlev, yalnızca sınıf yerel olmayan bir sınıfsa (9.8), işlev adı niteliksizse ve işlevin ad alanı kapsamı varsa, sınıfın arkadaş bildiriminde tanımlanabilir.

Böyle bir işlev dolaylı olarak satır içi. Bir sınıfta tanımlanan bir arkadaş işlevi, tanımlandığı sınıfın (sözcüksel) kapsamındadır. Sınıf dışında tanımlanan bir arkadaş işlevi değildir.


2
Aslında, arkadaş işlevleri standart C ++ 'da çevreleyen ad alanına yerleştirilmez. Eski davranışa "arkadaş adı enjeksiyonu" adı verildi, ancak birinci standartta değiştirilen ADL'nin yerini aldı. Üst bakın bu . (Yine de davranış oldukça benzer.)
GManNickG

1
Gerçekten eşdeğer değil. Söz konusu kod, swapsorguyu yalnızca ADL tarafından görülebilir hale getirir . Çevreleyen ad alanının bir üyesidir, ancak adı diğer ad arama formlarında görünmez. EDIT: @ GMan tekrar daha hızlı olduğunu görüyorum :) @ Ben ISO C ++ her zaman böyle oldu :)
Johannes Schaub - litb

2
@Ben: Hayır, arkadaş enjeksiyonu hiçbir zaman bir standartta yoktu, ancak daha önce yaygın olarak kullanıldı, bu yüzden fikir (ve derleyici desteği) devam ediyordu, ancak teknik olarak orada değil. friendişlevler yalnızca ADL tarafından bulunur ve yalnızca frienderişime sahip serbest işlevler olması gerekiyorsa, bunların hem friendsınıf içinde hem de sınıf dışında normal bir serbest işlev bildirimi olarak bildirilmesi gerekir. Bu zorunluluğu örneğin bu cevapta görebilirsiniz .
GManNickG

2
@towi: Arkadaş işlevi ad alanı kapsamında olduğundan, sorularınızın üçüne de yanıtlar netleşmelidir: (1) Ücretsiz bir işlevdir, ayrıca sınıfın özel ve korunan üyelerine arkadaş erişimi vardır. (2) Ne üye ne de statiktir. (3) ADL sınıflar içinde arama yapmaz, ancak arkadaş işlevinin ad alanı kapsamı olduğu için bu sorun olmaz.
Ben Voigt

1
@Ben. Spesifikasyonda, fonksiyon bir ad alanı üyesidir ve "fonksiyonun ad alanı kapsamına sahiptir" ifadesi, fonksiyonun bir ad alanı üyesi olduğunu söylemek için yorumlanabilir (hemen hemen böyle bir ifadenin bağlamına bağlıdır). Ve bu ad alanına yalnızca ADL tarafından görülebilen bir ad ekler (aslında, IIRC'nin bir kısmı, herhangi bir adın eklenip eklenmediğine ilişkin spesifikasyondaki diğer bölümlerle çelişir. ad, yani aslında, görünmez bir isim olduğunu ekledi. 3.3.1p4 nota bakın).
Johannes Schaub - litb
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.