<< operatörü bir arkadaş olarak mı yoksa bir üye işlevi olarak mı uygulanmalıdır?


129

Temelde soru bu, uygulamanın "doğru" bir yolu var operator<<mı? Okuma bu I gibi o şey görebilirsiniz:

friend bool operator<<(obj const& lhs, obj const& rhs);

gibi bir şeye tercih edilir

ostream& operator<<(obj const& rhs);

Ama neden birini veya diğerini kullanmam gerektiğini tam olarak anlayamıyorum.

Kişisel durumum:

friend ostream & operator<<(ostream &os, const Paragraph& p) {
    return os << p.to_str();
}

Ama muhtemelen yapabilirim:

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

Bu kararı hangi gerekçeye dayandırmalıyım?

Not :

 Paragraph::to_str = (return paragraph) 

burada paragraf bir dizedir.


4
BTW muhtemelen üye işlevlerinin imzalarına const eklemelisiniz
Motti

4
Neden << operatöründen bool döndürülsün? Bunu bir akış operatörü olarak mı yoksa bitsel kaydırmanın aşırı yüklenmesi olarak mı kullanıyorsunuz?
Martin York

Yanıtlar:


120

Buradaki sorun, bağladığınız makaleyi yorumlamanızdır .

eşitlik

Bu makale bool ilişki işleçlerini doğru bir şekilde tanımlamada sorun yaşayan biriyle ilgilidir.

Operatör:

  • Eşitlik == ve! =
  • İlişki <> <=> =

Bu operatörler, aynı türden iki nesneyi karşılaştırırken bir bool döndürmelidir. Bu operatörleri sınıfın bir parçası olarak tanımlamak genellikle en kolayıdır. Bunun nedeni, bir sınıfın otomatik olarak kendisinin bir arkadaşı olmasıdır, böylece Paragraf türü nesneler birbirlerini inceleyebilir (hatta birbirlerinin özel üyeleri).

Bu bağımsız işlevleri yapmak için bir argüman vardır, çünkü bu, aynı türde değilse her iki tarafı otomatik dönüştürmeye izin verirken, üye işlevleri yalnızca rh'lerin otomatik olarak dönüştürülmesine izin verir. İlk etapta (genellikle) otomatik dönüşümün olmasını gerçekten istemediğiniz için bunu bir kağıt adam argümanı buluyorum. Ancak bu istediğiniz bir şeyse (tavsiye etmiyorum), karşılaştırıcıları ayakta tutmak avantajlı olabilir.

Yayın Akışı

Akış operatörleri:

  • operatör << çıktı
  • operatör >> girişi

Bunları akış işleçleri olarak kullandığınızda (ikili kaydırma yerine) ilk parametre bir akımdır. Akış nesnesine erişiminiz olmadığından (değiştirmek size ait değildir) bunlar üye işleçler olamazlar, bunlar sınıfın dışında olmalıdır. Bu nedenle, ya sınıfın arkadaşları olmalı ya da sizin için akışı gerçekleştirecek genel bir yönteme erişimleri olmalıdır.

Bu nesnelerin bir akış nesnesine bir referans döndürmesi de gelenekseldir, böylece akış işlemlerini birlikte zincirleyebilirsiniz.

#include <iostream>

class Paragraph
{
    public:
        explicit Paragraph(std::string const& init)
            :m_para(init)
        {}

        std::string const&  to_str() const
        {
            return m_para;
        }

        bool operator==(Paragraph const& rhs) const
        {
            return m_para == rhs.m_para;
        }
        bool operator!=(Paragraph const& rhs) const
        {
            // Define != operator in terms of the == operator
            return !(this->operator==(rhs));
        }
        bool operator<(Paragraph const& rhs) const
        {
            return  m_para < rhs.m_para;
        }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};

std::ostream & operator<<(std::ostream &os, const Paragraph& p)
{
    return os << p.to_str();
}


int main()
{
    Paragraph   p("Plop");
    Paragraph   q(p);

    std::cout << p << std::endl << (p == q) << std::endl;
}

19
Neden operator<< private:?
Matt Clarkson

47
@MattClarkson: Değil. Bir arkadaş işlevi bildirimi bu nedenle sınıfın bir parçası değildir ve bu nedenle erişim belirteçlerinden etkilenmez. Genelde arkadaş işlevi bildirimlerini eriştikleri verilerin yanına koyarım.
Martin York

12
Verilere erişmek için genel bir işlevi kullanıyorsanız, neden dostane bir işlev olması gerekiyor? Üzgünüm, soru aptalcaysa.
Semyon Danilov

4
@SemyonDanilov: Neden kapsüllemeyi kırıp alıcılar ekliyorsunuz? freiendgenel arabirimi kapsüllemeyi bozmadan genişletmenin bir yoludur. Programmers.stackexchange.com/a/99595/12917'yi
Martin York

3
@LokiAstari Ama elbette bu, to_str'yi kaldırmak veya onu özel yapmak için bir argüman. Mevcut haliyle, akış operatörünün sadece genel işlevleri kullandığı için arkadaş olması gerekmez.
deworde

53

Bunu bir üye işlevi olarak yapamazsınız, çünkü örtük thisparametre <<-operatörün sol tarafındadır . (Bu nedenle, onu ostream-sınıfa üye işlevi olarak eklemeniz gerekir . İyi değil :)

Bunu yapmadan ücretsiz bir işlev olarak yapabilir misiniz friend? Tercih ettiğim şey bu, çünkü bunun ostreamsınıfınızın temel bir işlevi değil, onunla bir bütünleşme olduğunu açıkça gösteriyor .


1
"sınıfınızın temel bir işlevi değildir." "Arkadaş" bu demektir. Temel işlevsellik olsaydı, sınıfta olurdu, arkadaş olmazdı.
xaxxon

1
@xaxxon Sanırım ilk cümlenin bu durumda işlevi üye işlevi olarak eklemenin neden imkansız olduğunu açıklıyor. Bir friendişlev üye işlevle aynı haklara sahiptir ( bu ne friendanlama gelir), bu nedenle sınıfın bir kullanıcısı olarak neden buna ihtiyaç duyduğunu merak etmeliyim. Bu, "temel işlevsellik" ifadesiyle yapmaya çalıştığım ayrımdır.
Magnus Hoff

32

Mümkünse üye olmayan ve arkadaş olmayan işlevler olarak.

Herb Sutter ve Scott Meyers tarafından açıklandığı gibi, kapsüllemeyi artırmaya yardımcı olmak için arkadaş olmayan üye olmayan işlevleri üye işlevlere tercih edin.

C ++ akışları gibi bazı durumlarda, seçeneğiniz olmaz ve üye olmayan işlevleri kullanmanız gerekir.

Ancak yine de, bu işlevleri sınıflarınızın arkadaşları yapmanız gerektiği anlamına gelmez: Bu işlevler, sınıf erişimcileriniz aracılığıyla sınıfınıza yine de erişebilir. Bu fonksiyonları bu şekilde yazmayı başarırsanız, o zaman kazanırsınız.

Operatör << ve >> prototipleri hakkında

Sorunuzda verdiğiniz örneklerin yanlış olduğuna inanıyorum. Örneğin;

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

Bu yöntemin bir akışta nasıl çalışacağını düşünmeye bile başlayamıyorum.

İşte << ve >> operatörlerini uygulamanın iki yolu.

T türünde akış benzeri bir nesne kullanmak istediğinizi varsayalım.

Ve Paragraf türündeki nesnenizin ilgili verilerini T'den / içine çıkarmak / eklemek istediğinizi.

Genel operatör << ve >> işlev prototipleri

Birincisi işlev olarak:

// T << Paragraph
T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// T >> Paragraph
T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return p_oInputStream ;
}

Genel operatör << ve >> yöntem prototipleri

İkincisi, yöntem olarak:

// T << Paragraph
T & T::operator << (const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return *this ;
}

// T >> Paragraph
T & T::operator >> (const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return *this ;
}

Bu gösterimi kullanmak için T'nin sınıf bildirimini genişletmeniz gerektiğini unutmayın. STL nesneleri için bu mümkün değildir (onları değiştirmeniz gerekmez ...).

Peki ya T bir C ++ akışı ise?

C ++ akışları için aynı << ve >> operatörlerinin prototipleri aşağıda verilmiştir.

Genel basic_istream ve basic_ostream için

C ++ akışını değiştiremeyeceğiniz için, akışlar söz konusu olduğunda, işlevleri uygulamanız gerektiğini unutmayın. Bunun anlamı şunun gibi:

// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Char istream ve ostream için

Aşağıdaki kod yalnızca karakter tabanlı akışlar için çalışacaktır.

// OUTPUT << A
std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> A
std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Rhys Ulerich, karakter tabanlı kodun üstündeki genel kodun bir "uzmanlığı" olduğu gerçeği hakkında yorum yaptı. Tabii ki, Rhys haklı: Char temelli örneğin kullanılmasını önermiyorum. Sadece okuması daha kolay olduğu için burada verilmiştir. Yalnızca char tabanlı akışlarla çalışıyorsanız uygulanabilir olduğundan, wchar_t kodunun yaygın olduğu platformlarda (yani Windows'ta) bundan kaçınmalısınız.

Umarım bu yardımcı olur.


Genel basic_istream ve basic_ostream şablon kodunuz zaten std :: ostream- ve std :: istream'e özgü sürümleri kapsamıyor mu?
Rhys Ulerich

@Rhys Ulerich: Elbette. Yalnızca genel, şablonlu sürümü kullanıyorum, çünkü Windows'ta hem char hem de wchar_t kodu ile uğraşmak zorundasın. İkinci versiyonun tek başarısı, birincisinden daha basit görünmesidir. Bununla ilgili yazımı netleştireceğim.
paercebal

10

Özellikle bugünlerde çoğu şey gibi çıktı esas olarak teşhis ve kayıt için kullanılıyorsa, ücretsiz, arkadaş olmayan işlevler olarak uygulanmalıdır. Çıktıya gitmesi gereken her şey için const erişimcileri ekleyin ve ardından çıktının yalnızca bunları çağırmasını ve biçimlendirmesini sağlayın.

Aslında tüm bu ostream çıktı içermeyen işlevleri bir "ostreamhelpers" başlığında ve uygulama dosyasında toplamaya başladım, bu ikincil işlevselliği sınıfların gerçek amacından uzak tutuyor.


7

İmza:

bool operator<<(const obj&, const obj&);

Oldukça şüpheli görünüyor, bu streamne sözleşmeye ne de bit tabanlı kurala uymuyor, bu nedenle operatörün aşırı yüklenme kötüye kullanımı durumu gibi görünüyor, operator <geri dönmeli boolamaoperator << muhtemelen başka bir şey döndürmelidir.

Öyle diyorsan söyle:

ostream& operator<<(ostream&, const obj&); 

Daha sonra ostream, zorunlu olarak işlev ekleyemeyeceğiniz için , işlevin ücretsiz bir işlev olması gerekir friend, neye erişmesi gerektiğine bağlıdır (özel veya korumalı üyelere erişmesi gerekmiyorsa, bunu yapmaya gerek yoktur) arkadaş).


Siparişi ostreamkullanırken değişiklik için erişim gerekeceğinden bahsetmeye değer ostream.operator<<(obj&); dolayısıyla özgür işlev. Aksi takdirde, erişim sağlamak için kullanıcı türünün bir buhar türü olması gerekir.
wulfgarpro

2

Sadece tamamlama uğruna, sana gerçekten o eklemek istiyorum edebilir bir operatör oluşturmak ostream& operator << (ostream& os)bir sınıf içinde ve o çalışabilir. Bildiğim kadarıyla onu kullanmak iyi bir fikir değil çünkü çok karmaşık ve sezgisel değil.

Bu koda sahip olduğumuzu varsayalım:

#include <iostream>
#include <string>

using namespace std;

struct Widget
{
    string name;

    Widget(string _name) : name(_name) {}

    ostream& operator << (ostream& os)
    {
        return os << name;
    }
};

int main()
{
    Widget w1("w1");
    Widget w2("w2");

    // These two won't work
    {
        // Error: operand types are std::ostream << std::ostream
        // cout << w1.operator<<(cout) << '\n';

        // Error: operand types are std::ostream << Widget
        // cout << w1 << '\n';
    }

    // However these two work
    {
        w1 << cout << '\n';

        // Call to w1.operator<<(cout) returns a reference to ostream&
        w2 << w1.operator<<(cout) << '\n';
    }

    return 0;
}

Özetlemek gerekirse - bunu yapabilirsiniz, ancak büyük olasılıkla yapmamalısınız :)


0

arkadaş operatörü = sınıfla eşit haklar

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}

0

operator<< arkadaş işlevi olarak uygulandı:

#include <iostream>
#include <string>
using namespace std;

class Samp
{
public:
    int ID;
    string strName; 
    friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
};
 std::ostream& operator<<(std::ostream &os, const Samp& obj)
    {
        os << obj.ID<<   << obj.strName;
        return os;
    }

int main()
{
   Samp obj, obj1;
    obj.ID = 100;
    obj.strName = "Hello";
    obj1=obj;
    cout << obj <<endl<< obj1;

} 

ÇIKTI:
100 Merhaba
100 Merhaba

Bu bir arkadaş işlevi olabilir, çünkü nesne öğesinin sağ tarafında operator<<ve bağımsız değişken coutsol taraftadır. Yani bu, sınıfa üye bir işlev olamaz, yalnızca bir arkadaş işlevi olabilir.


Bunu üye işlevi olarak yazmanın bir yolu olduğunu sanmıyorum !!
Rohit Vipin Mathews

Neden her şey cesur. Bunu kaldırmama izin ver.
Sebastian Mach
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.