SORU
24 Mart 2013, Pazar


Nasıl parametreleri doğru geçmek için?

Diğer sorularıma da söylediğim gibi, C acemi ama programlama bir acemi değilim. C (c 11) öğrenmeye çalışıyorum ve bana çok önemli bir şey için biraz belirsiz: parametreleri passing.

Basit örnekler olarak bunlar

  • Tüm üyeler ilkel türleri olan bir sınıf
    CreditCard(std::string number, int expMonth, int expYear,int pin):number(number), expMonth(expMonth), expYear(expYear), pin(pin)

  • Üye olarak bir sınıf ilkel tip 1 kompleks türü:
    Account(std::string number, float amount, CreditCard creditCard) : number(number), amount(amount), creditCard(creditCard)

  • Biraz karmaşık tür ilkel türleri 1 Üye toplama gibi bir sınıf: Client(std::string firstName, std::string lastName, std::vector<Account> accounts):firstName(firstName), lastName(lastName), accounts(accounts)

Bir hesap oluşturmak:

    CreditCard cc("12345",2,2015,1001);
    Account acc("asdasd",345, cc);

Belli ki kredi kartı iki kez bu senaryoda kopyalanacaktır. Ben bu kadar yapıcı yeniden yazmak durumunda

Account(std::string number, float amount, CreditCard& creditCard) 
    : number(number)
    , amount(amount)
    , creditCard(creditCard)

bir kopyası olacak. Benim gibi yeniden yazmak

Account(std::string number, float amount, CreditCard&& creditCard) 
    : number(number)
    , amount(amount)
    , creditCard(std::forward<CreditCard>(creditCard))

2 hamle ve kopya yok.

Bazen bazı parametre kopyalamak isteyebilirsiniz düşünüyorum, bazen o nesne oluşturduğunuzda kopyalamak istemiyorum.
C geldiğim# ve referanslar için kullanılan, bana biraz garip geliyor ve her parametre için 2 aşırı olmalı diye düşünüyorum ama yanılıyorum biliyorum.
Gerçekten bulmak çünkü C parametreleri göndermek için nasıl en iyi uygulamaları, diyelim ki, önemsiz değildir. Nasıl benim yukarıdaki örnekler sundu yapardın?
Bu okuma için teşekkürler. Yeni bir şeyler öğrenebilirim diye yanıt bekliyoruz ben.

CEVAP
24 Mart 2013, Pazar


EN ÖNEMLİ SORU İLK:

Gerçekten bulmak çünkü C parametreleri göndermek için nasıl en iyi uygulamaları, diyelim ki, önemsiz değildir

Eğer bilgisayarınızın çalışması için ihtiyacı varsadeğiştirinorijinal çağrı döndükten sonra, bu nesne için değişiklik arayana görünür olarak geçti, nesne, sonra geçerlvalue başvuru:

void foo(my_class& obj)
{
    // Modify obj here...
}

Fonksiyonorijinal nesneyi değiştirmek için gerekli değildir, ve bir kopyasını oluşturmak için gerekli değildir(diğer bir deyişle, sadece kendi durumunu gözlemek gerekiyor), sonra geçerconst referans lvalue:

void foo(my_class const& obj)
{
    // Observe obj here
}

Bu rvalues ile lvalues (lvalues istikrarlı bir kimlik ile nesneleri) ve (rvalues, örneğin . her iki işlevi çağırmak için izin verir ^em>geçici** 24) arama sonucu yere gitmek üzeresin , ya da nesneleri.

Ayrıca iddia olabilirtemel tür veya türleri için hızlı kopyalama,*,* 26 int veya işlevi sadece değer gözlemlemek gerekiyorsa referans geçmek için gerek yoktur, ve. char gibi ^strong>değer olarak geçirilmesi tercih edilmelidir. Bu doğru eğerbaşvuru anlambilimgerekli değildir, ama ne olursa işlevi istedim saklamak için bir işaretçi için aynı giriş nesnesi bir yere, böylece gelecekteki okur ile bir işaretçi göreceksiniz değeri değişiklikleri gerçekleştirildiğini başka bölüm kodu? Bu durumda, başvuru olarak geçirilmesi doğru bir çözüm değildir.

Fonksiyonorijinal nesneyi değiştirmek için gerekli değildir, ama o nesnenin bir kopyasını saklamak gerekir(muhtemelen giriş değiştirmeden giriş dönüşümün bir sonucu dönmek için), o zaman düşünebilirizdeğer alarak:

void foo(my_class obj) // One copy or one move here, but not working on
                       // the original object...
{
    // Working on obj...

    // Possibly move from obj if the result has to be stored somewhere...
}

Yukarıdaki fonksiyon çağırma her zaman lvalues geçerken bir kopyasını neden olur, ve rvalues geçerken bir hamle. Eğer işlevi, bu nesne bir yere saklamak gerekiyorsa, ek gerçekleştirebilirhareket ettirin(örneğin, bu durumda foo() a member function that needs to store the value in a data member).

Durumda hareket pahalıdıriçin nesne türü my_class, sonra da bir düşünün aşırı yükleme foo() ve size bir sürümü için lvalues (kabul eden bir lvalue başvuru için const) ve bir sürüm için rvalues (kabul eden bir rvalue reference):

// Overload for lvalues
void foo(my_class const& obj) // No copy, no move (just reference binding)
{
    my_class copyOfObj = obj; // Copy!
    // Working on copyOfObj...
}

// Overload for rvalues
void foo(my_class&& obj) // No copy, no move (just reference binding)
{
    my_class copyOfObj = std::move(obj); // Move! 
                                         // Notice, that invoking std::move() is 
                                         // necessary here, because obj is an
                                         // *lvalue*, even though its type is 
                                         // "rvalue reference to my_class".
    // Working on copyOfObj...
}

Fonksiyonları tek bir fonksiyon yapman çok benzer, aslında, yukarıdaki: foo() bir fonksiyonu haline gelebilirşablonve kullanabilirsinizmükemmel iletmebir hareket ya da nesne geçirilen bir kopyasını DAHİLİ olarak oluşturulan olup olmadığını belirlemek için:

template<typename C>
void foo(C&& obj) // No copy, no move (just reference binding)
//       ^^^
//       Beware, this is not always an rvalue reference! This will "magically"
//       resolve into my_class& if an lvalue is passed, and my_class&& if an
//       rvalue is passed
{
    my_class copyOfObj = std::forward<C>(obj); // Copy if lvalue, move if rvalue
    // Working on copyOfObj...
}

Daha bu tasarım hakkında this talk by Scott Meyers izleyerek öğrenmek isteyebilirsiniz (sadece gerçeği terimi zihin "Evrensel Referanslar"kullanıyor bu standart olmayan) em.

Akılda tutulması gereken bir şey std::forward genellikle bir'de sona erecekhareket ettirinnispeten masum görünse de çok rvalues için, iletme aynı nesneyi birden çok kez sıkıntı kaynağı - örneğin, aynı nesne iki kez hareket olabilir! Bu yüzden bir işlev çağrısı öne aynı değişkeni birden çok kez bir döngü içinde ve bu koymak için dikkatli olun:

template<typename C>
void foo(C&& obj)
{
    bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}

Ayrıca normalde bunun için iyi bir nedeniniz yoksa şablon tabanlı çözüm çare değil bu, kodunuzun okunmasını daha da zorlaştıracak gibi dikkat edin.Normalde, açıklık ve basitlik odaklanmak gerekir.

Yukarıda sadece basit kurallar vardır, ama çoğu zaman iyi tasarım kararları doğru bir işaret olacaktır.


YAZINIZI GERİ KALANI İLE İLGİLİ:

Ben olarak yeniden [...] 2 hamle ve kopya yok.

Bu doğru değil. Bir lvalue, rvalue başvuru yapamaz bağlama başlamak için, bu tek tip bir rvalue geçirmeden derlenir kurucu CreditCard. Örneğin:

// Here you are passing a temporary (OK! temporaries are rvalues)
Account acc("asdasd",345, CreditCard("12345",2,2015,1001));

CreditCard cc("12345",2,2015,1001);
// Here you are passing the result of std::move (OK! that's also an rvalue)
Account acc("asdasd",345, std::move(cc));

Ama eğer bunu yapmak için çalışırsanız işe yaramaz:

CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue

cc lvalues için lvalue ve rvalue referansları olan bir bağlama. Ayrıcabir nesneye bir başvuru bağlama, hareket gerçekleştirilirsadece bir referans bağlayıcı olur. Bu nedenle, sadece olacakbirhareket ettirin.


Bu yüzden Temel Kurallar verilen ilk bölümünde bu cevap, eğer endişe ile numarasını taşır varlık oluşturulduğunda, bir CreditCard değeri olabilir tanımlamak iki kurucu aşırı yükleme, bir alan bir lvalue başvuru için const (CreditCard const&) ve bir alan bir rvalue başvuru (CreditCard&&).

Aşırı çözümleme bir rvalue (bu durumda, bir hareket yapılır) geçerken bir lvalue (bu durumda, bir kopyası yapılır) ve ikinci geçerken eski seçecektir.

Account(std::string number, float amount, CreditCard const& creditCard) 
: number(number), amount(amount), creditCard(creditCard) // copy here
{ }

Account(std::string number, float amount, CreditCard&& creditCard) 
: number(number), amount(amount), creditCard(std::move(creditCard)) // move here
{ }

std::forward<> kullanımınızı normalde elde etmek istediğiniz zaman görülürmükemmel iletme. Bu durumda, kurucu aslında bir kurucu olurduşablonve bak ki gibi daha fazla veya daha az

template<typename C>
Account(std::string number, float amount, C&& creditCard) 
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }

Bir anlamda, bu birleştirir hem de aşırı ettim gösterilen daha önce bir tek fonksiyon: C olacak sonucuna CreditCard& vakasın geçen bir lvalue, ve nedeniyle başvuru çöken kuralları neden olacak bu işlev için oluşturulmuş:

Account(std::string number, float amount, CreditCard& creditCard) : 
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard)) 
{ }

Bu bir neden olacaktırkopyala-inşaatistediğiniz gibi creditCard. Bir rvalue geçirildiğinde diğer taraftan, C CreditCard, çıkarılabilir olacak ve bu işlevi yerine oluşturulacak:

Account(std::string number, float amount, CreditCard&& creditCard) : 
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard)) 
{ }

Bu bir neden olacaktırhareket-inşaatseni istiyorum creditCard değer geçirilen bir rvalue ve taşımak için yetkimiz olduğu anlamına gelir çünkü).

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Dave Wallace

    Dave Wallace

    27 Kasım 2007
  • iMasterful

    iMasterful

    11 EYLÜL 2009
  • kindlechatmail

    kindlechatma

    25 AĞUSTOS 2010