Std :: swap () nasıl aşırı yüklenir


115

std::swap()birçok standart konteyner tarafından kullanılır (örneğin std::listvestd::vector ) tarafından sıralama ve hatta atama sırasında kullanılır.

Ancak swap()öğesinin standart uygulaması çok genelleştirilmiştir ve özel türler için oldukça verimsizdir.

Böylece, std::swap()özel tipe özgü bir uygulama ile aşırı yükleme ile verimlilik elde edilebilir . Ancak, standart konteynerler tarafından kullanılması için onu nasıl uygulayabilirsiniz?



Değiştirilebilir sayfa taşındı en.cppreference.com/w/cpp/named_req/Swappable
Andrew Keeton

Yanıtlar:


135

Takası aşırı yüklemenin doğru yolu, değiştirdiğinizle aynı ad alanına yazmaktır, böylece bağımsız değişkene bağlı arama (ADL) yoluyla bulunabilir . Yapması özellikle kolay olan bir şey şudur:

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};

11
C ++ 2003'te en iyi ihtimalle yetersiz belirtilmiştir. Çoğu uygulama, takas bulmak için ADL kullanır, ancak hayır, zorunlu değildir, bu yüzden ona güvenemezsiniz. Sen edebilirsiniz OP ile gösterildiği gibi, belirli bir beton türü için std :: takas uzmanlaşmak; sadece bu uzmanlığın kullanılmasını beklemeyin, örneğin bu türden türetilmiş sınıflar için.
Dave Abrahams

15
Gerçek takası bulmak için uygulamaların hala ADL kullanmadığını görmek beni şaşırtabilir . Bu komitede eski bir mesele. Uygulamanız takas bulmak için ADL kullanmıyorsa bir hata raporu oluşturun.
Howard Hinnant

3
@Sascha: İlk olarak, işlevi ad alanı kapsamında tanımlıyorum çünkü genel kod için önemli olan tek tanım türü bu. Çünkü int et. ark. üye işlevleri yoktur / olamaz, std :: sort et. ark. ücretsiz bir işlev kullanmak zorunda; protokolü oluştururlar. İkinci olarak, neden iki uygulamaya itiraz ettiğinizi bilmiyorum, ancak üye olmayan bir takas yapmayı kabul edemezseniz çoğu sınıf verimsiz bir şekilde sıralanmaya mahkumdur. Aşırı yükleme kuralları, her iki bildirimin de görülmesi halinde, daha spesifik olanın (bu), niteliksiz takas çağrısı yapıldığında seçilmesini sağlar.
Dave Abrahams

5
@ Mozza314: Duruma göre değişir. Bir std::sortdeğiştirilebilir elemanlarına ADL kullandığı 03 C ++ uygun olmayan, ancak C ++ 11 uygun olan. Ayrıca, neden -1, istemcilerin deyimsel olmayan kod kullanabileceği gerçeğine dayalı bir yanıt?
JoeG

4
@curiousguy: Eğer standardı okumak sadece standardı okumakla ilgili basit bir meseleyse, haklısınız :-). Ne yazık ki yazarların niyeti önemli. Dolayısıyla, asıl amaç ADL'nin kullanılabileceği veya kullanılması gerektiği yönündeyse, yetersiz belirtilmiştir. Değilse, o zaman bu sadece C ++ 0x için eski bir kırılma değişikliğidir, bu yüzden "en iyi ihtimalle" eksik belirtilmiş olarak yazdım.
Dave Abrahams

70

Dikkat Mozza314

Burada, genel bir std::algorithmaramanın etkilerinin bir simülasyonu std::swapve kullanıcının ad alanı std'deki takaslarını sağlaması. Bu bir deney olduğundan, bu simülasyon namespace expyerine kullanır namespace std.

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Benim için bu çıktı:

generic exp::swap

Derleyiciniz farklı bir şey yazdırırsa, şablonlar için "iki aşamalı arama" işlemini doğru bir şekilde gerçekleştirmiyor demektir.

Derleyiciniz uyumluysa (C ++ 98/03 / 11'den herhangi birine), o zaman gösterdiğim çıktıyı verecektir. Ve bu durumda, olacağından tam olarak korktuğunuz şey olur. Ve swapad alanına std( exp) koymanız onun olmasını engellemedi.

Dave ve ben her ikimiz de komite üyeleriyiz ve on yıldır standardın bu alanında çalışıyoruz (ve her zaman birbirleriyle aynı fikirde değiliz). Ama bu mesele uzun zamandır çözüldü ve ikimiz de bunun nasıl çözüldüğü konusunda hemfikiriz. Dave'in bu alandaki uzman görüşünü / cevabını kendi sorumluluğunuzda göz ardı edin.

Bu sorun, C ++ 98 yayınlandıktan sonra gün yüzüne çıktı. 2001 yılından itibaren Dave ve ben bu alanda çalışmaya başladık . Ve bu modern çözüm:

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Çıktı:

swap(A, A)

Güncelleme

Şu şekilde bir gözlem yapılmıştır:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

İşler! Öyleyse neden bunu kullanmıyorsun?

ABir sınıf şablonu olduğu durumu düşünün :

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

Şimdi tekrar çalışmıyor. :-(

Böylece swapstd ad alanına koyabilir ve çalışmasını sağlayabilirsiniz. Ama koymak hatırlamak gerekir swapiçinde Abir şablon olduğunda durum için bireyin ad: A<T>. Eğer koyarsanız hem vakalar çalışacaktır beri swapde A'ın ad, (ve öğretme başkalarına) sadece bunu tek yönlü olduğunu hatırlamak sadece daha kolaydır.


4
Detaylı cevap için çok teşekkür ederim. Bu konuda açıkça daha az bilgiliyim ve aslında aşırı yükleme ve uzmanlaşmanın nasıl farklı davranışlar üretebileceğini merak ediyordum. Bununla birlikte, aşırı yüklemeyi değil, uzmanlaşmayı öneriyorum. template <>İlk örneğinizi koyduğumda exp::swap(A, A)gcc'den çıktı alıyorum . Peki neden uzmanlaşmayı tercih etmiyorsunuz?
voltrevo

1
Vaov! Bu gerçekten aydınlatıcı. Beni kesinlikle ikna ettin. Sanırım önerinizi biraz değiştireceğim ve Dave Abrahams'ın sınıf içi arkadaş sözdizimini kullanacağım (hey bunu << operatörü için de kullanabilirim! :-)), bundan kaçınmak için bir nedeniniz yoksa (derleme dışında) ayrı ayrı). Ayrıca, bunun ışığında using std::swap, "başlık dosyalarının içine asla deyimler koymayın" kuralının bir istisnası olduğunu düşünüyor musunuz? Aslında, neden içine using std::swapkoymuyoruz <algorithm>? Sanırım küçük bir azınlık insan kodunu kırabilir. Belki desteği reddetmek ve sonunda yerine koymak?
voltrevo

3
sınıf içi arkadaş sözdizimi iyi olmalıdır. using std::swapBaşlıklarınızdaki işlev kapsamını sınırlamaya çalışırım . Evet, swapneredeyse bir anahtar kelimedir. Ama hayır, bu tam olarak bir anahtar kelime değil. Bu yüzden, gerçekten zorunda kalana kadar onu tüm ad alanlarına aktarmamak en iyisidir. swapçok benziyor operator==. En büyük fark, hiç kimsenin operator==nitelikli ad alanı sözdizimi ile arama yapmayı düşünmemesidir (çok çirkin olur).
Howard Hinnant

15
@NielKirk: Karmaşıklık olarak gördüğünüz şey sadece çok fazla yanlış cevap. Dave Abrahams'ın doğru cevabıyla ilgili karmaşık bir şey yok: "Takası aşırı yüklemenin doğru yolu, değiş tokuşa bağlı arama (ADL) yoluyla bulunabilmesi için takas ettiğinizle aynı ad alanına yazmaktır."
Howard Hinnant

2
@codeshot: Üzgünüm. Herb, 1998'den beri bu mesajı iletmeye çalışıyor: gotw.ca/publications/mill02.htm Bu makalede swap'tan bahsetmiyor. Ancak bu, Herb's Interface Principle'ın başka bir uygulamasıdır.
Howard Hinnant

53

(C ++ standardına göre) std :: swap'i aşırı yüklemenize izin verilmez, ancak std ad alanına kendi türleriniz için özel şablon özelleştirmeleri eklemenize özellikle izin verilir. Örneğin

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

o zaman std kapsayıcılarındaki (ve başka herhangi bir yerdeki) kullanımlar, genel olanın yerine uzmanlığınızı seçecektir.

Ayrıca, bir temel sınıf takas uygulaması sağlamanın, türetilmiş türleriniz için yeterli olmadığını unutmayın. Örneğin eğer varsa

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

bu Temel sınıflar için çalışacaktır, ancak iki Türetilmiş nesneyi takas etmeye çalışırsanız, std'den genel sürümü kullanacaktır çünkü şablonlu takas tam bir eşleşmedir (ve türetilmiş nesnelerinizin yalnızca 'temel' kısımlarını değiştirme sorununu ortadan kaldırır. ).

NOT: Son cevabımdan yanlış bitleri kaldırmak için bunu güncelledim. D'oh! (işaret ettiği için puetzk ve j_random_hacker'a teşekkürler)


1
Çoğunlukla iyi bir tavsiye, ancak puetzk tarafından std ad alanında (C ++ standardı tarafından izin verilir) bir şablonu özelleştirme ile aşırı yükleme (ki bu değildir) arasındaki ince ayrım nedeniyle -1'e ihtiyacım var.
j_random_hacker

11
Olumsuz oy verildi çünkü takası özelleştirmenin doğru yolu, bunu kendi ad alanınızda yapmaktır (Dave Abrahams'ın başka bir cevapta işaret ettiği gibi).
Howard Hinnant

2
Olumsuz oy verme nedenlerim Howard'ınki ile aynı
Dave Abrahams

13
@HowardHinnant, Dave Abrahams: Katılmıyorum. Hangi temelde alternatifinizin "doğru" yol olduğunu iddia ediyorsunuz? Puetzk'in standarttan alıntı yaptığı gibi, buna özellikle izin verilir. Bu konuda yeniyken, savunduğunuz yöntemi gerçekten sevmiyorum çünkü Foo'yu tanımlarsam ve kodumu kullanan bir başkası bu şekilde değiştirirsem, muhtemelen takas ( a, b) verimsiz varsayılan sürümü sessizce kullanan Foo'da.
voltrevo

5
@ Mozza314: Yorum alanının alanı ve biçimlendirme kısıtlamaları size tam olarak yanıt vermeme izin vermedi. Lütfen "Mozza314 Dikkat" başlıklı eklediğim yanıta bakın.
Howard Hinnant

29

Genellikle std :: ad alanına bir şeyler eklenmemesi gerektiği doğru olsa da, kullanıcı tanımlı türler için şablon uzmanlıkları eklemeye özellikle izin verilir. Fonksiyonların aşırı yüklenmesi değil. Bu ince bir fark :-)

17.4.3.1/1 Bir C ++ programının, aksi belirtilmedikçe std ad alanına veya ad alanlarına std ad alanına bildirimler veya tanımlar eklemesi tanımsızdır. Bir program, std ad alanına herhangi bir standart kitaplık şablonu için şablon uzmanlıkları ekleyebilir. Standart bir kitaplığın bu tür bir uzmanlığı (tam veya kısmi), bildirim, kullanıcı tanımlı bir dış bağlantı adına dayanmadığı ve şablon uzmanlığı orijinal şablon için standart kitaplık gereksinimlerini karşılamadığı sürece tanımsız davranışla sonuçlanır.

Std :: swap'ın bir uzmanlığı şöyle görünür:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

Şablon <> biti olmadan, izin verilen bir uzmanlaşma yerine tanımsız olan bir aşırı yük olur. @ Wilka'nın varsayılan ad alanını değiştirme yaklaşımı, kullanıcı koduyla çalışabilir (ad alansız sürümü tercih eden Koenig araması nedeniyle) ancak garanti edilmez ve aslında gerçekten de beklenmez (STL uygulaması, -kalifiye std :: takas).

Bir yoktur comp.lang.c üzerinde iplik ++. Yönetilir bir ile uzun konunun dicussion. Yine de çoğu kısmi uzmanlaşma ile ilgili (şu anda bunu yapmanın iyi bir yolu yok).


7
Bunun için (veya herhangi bir şey için) işlev şablonu uzmanlığını kullanmanın yanlış olmasının bir nedeni: takas için çok sayıda olan aşırı yüklemelerle kötü şekillerde etkileşime giriyor. Örneğin, standart std :: swap for std :: vector <mytype> & için uzmanlaşırsanız, uzmanlığınız standardın vektöre özgü swap yerine seçilmez, çünkü uzmanlıklar aşırı yük çözümü sırasında dikkate alınmaz.
Dave Abrahams

4
Bu aynı zamanda Meyers'in Etkili C ++ 3ed'de önerdiği şeydir (Madde 25, s. 106-112).
jww

2
@DaveAbrahams: Eğer (açık şablon argümanları olmadan) uzmanlaşırlarsa kısmi sıralama bir uzmanlık olmasına yol açar vevector sürümü ve kullanılacaktır .
Davis Herring

1
@DavisHerring aslında, hayır, bu kısmi siparişi yaptığınızda hiçbir rol oynamaz. Sorun, onu arayamayacak olmanız değil; görünüşte daha az spesifik olan takas aşırı yüklemelerinin varlığında olan şey budur: wandbox.org/permlink/nck8BkG0WPlRtavV
Dave Abrahams

2
@DaveAbrahams: Kısmi sıralama, açık uzmanlık birden fazla eşleştiğinde uzmanlaşmak için işlev şablonunu seçmektir . ::swapEğer ilave yük fazla uzmanlaşmıştır std::swapiçin aşırı yük vectoryüzden çağrı ve ikincisi alakalı hiçbir ihtisas yakalar. Bunun nasıl pratik bir problem olduğundan emin değilim (ama bunun iyi bir fikir olduğunu da iddia etmiyorum!).
Davis Herring
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.