Tek bir işlev çağrısı C ++ kullanarak birden çok sabit sınıf üyesini başlatma


50

Her ikisi de aynı işlev çağrısına dayalı olarak başlatılması gereken iki farklı sabit üye değişkenim varsa, işlevi iki kez çağırmadan bunu yapmanın bir yolu var mı?

Örneğin pay ve payda sabit olan bir kesir sınıfı.

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
    {

    }
private:
    const int numerator, denominator;
};

Bu, GCD işlevi iki kez çağrıldığından zaman kaybına neden olur. Ayrıca yeni bir sınıf üyesi tanımlayabilir gcd_a_bve ilk olarak gcd çıktısını başlatıcı listesinde atayabilirsiniz, ancak daha sonra boşa harcanmış olur.

Genel olarak, bunu işlev çağrıları veya bellek israfı olmadan yapmanın bir yolu var mı? Başlatıcı listesinde geçici değişkenler oluşturabilir misiniz? Teşekkür ederim.


5
"GCD işlevinin iki kez çağrıldığına" dair kanıtınız var mı? İki kez bahsedilir, ancak bu kodu iki kez çağıran bir kod yayan derleyici ile aynı şey değildir. Bir derleyici, bunun saf bir işlev olduğunu belirleyebilir ve değerini ikinci sözde tekrar kullanabilir.
Eric Towers

6
@EricTowers: Evet, derleyiciler bazen bazı durumlarda uygulamada problemi çözebilir. Ancak yalnızca tanımı (veya bir nesnede bazı ek açıklamaları) görebiliyorlarsa, bunun saf olduğunu kanıtlamanın bir yolu yoktur. Sen gerektiğini bağlantı zamanlı optimizasyonu etkinken derlemek, ama herkes yapar. Ve işlev bir kütüphanede olabilir. Ya da bir fonksiyonun durumu göz önüne yapar yan etkileri vardır ve tam olarak bir kez çağıran doğruluğu meselesidir?
Peter Cordes

@EricTowers İlginç bir nokta. Aslında GCD işlevinin içine bir print deyimi koyarak kontrol etmeye çalıştım, ama şimdi bunun saf bir işlev olmasını engelleyeceğini anladım.
Qq0

@ Soru0: Oluşturulan derleyiciye asm bakarak kontrol edebilirsiniz, örneğin gcc veya clang ile Godbolt derleyici gezgini kullanarak -O3. Ancak muhtemelen herhangi bir basit test uygulaması için aslında işlev çağrısını satır içine alacaktır. __attribute__((const))Görünür bir tanım sağlamadan prototip üzerinde veya saf kullanırsanız , GCC veya clang'ın aynı argümanla iki çağrı arasında ortak alt ifade eliminasyonu (CSE) yapmasına izin vermelidir. Drew'un cevabının saf olmayan fonksiyonlar için bile çalıştığını unutmayın, bu yüzden çok daha iyidir ve fonk satır içi olmayabilir her zaman kullanmalısınız.
Peter Cordes

Genellikle statik olmayan sabit üye değişkenlerden en iyi kaçınılmalıdır. Her şeyin sık sık uygulanmadığı birkaç alandan biri. Örneğin, sınıf nesneleri atayamazsınız. Bir vektör içine emplace_back yapabilirsiniz, ancak yalnızca kapasite sınırı yeniden boyutlandırmada başlamadığı sürece.
doug

Yanıtlar:


66

Genel olarak, bunu işlev çağrıları veya bellek israfı olmadan yapmanın bir yolu var mı?

Evet. Bu, C ++ 11'de tanıtılan bir temsilci kurucu ile yapılabilir .

Temsilci bir kurucu, herhangi bir üye değişken başlatılmadan önce inşaat için gerekli geçici değerleri elde etmenin çok etkili bir yoludur .

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Call gcd ONCE, and forward the result to another constructor.
    Fraction(int a, int b) : Fraction(a,b,gcd(a,b))
    {
    }
private:
    // This constructor is private, as it is an
    // implementation detail and not part of the public interface.
    Fraction(int a, int b, int g_c_d) : numerator(a/g_c_d), denominator(b/g_c_d)
    {
    }
    const int numerator, denominator;
};

Çıkarsız olarak, başka bir kurucu çağırmanın getirdiği ek yük önemli midir?
Qq0

1
@ Soru0 Burada , mütevazı optimizasyonların etkinleştirildiği bir ek yük olmadığını gözlemleyebilirsiniz .
Drew Dormann

2
Qq0: C ++ modern optimizasyon derleyicileri etrafında tasarlanmıştır. Özellikle .h, gerçek yapıcı tanımı satır içi için görünmese bile , sınıf tanımında (içinde ) görünür kılarsanız, bu heyeti önemsiz bir şekilde satır içine alabilirler. yani gcd()çağrı her kurucu çağrı sitesine satır içi olarak yazılır ve call3-işlenen özel kurucuya sadece a bırakır .
Peter Cordes

10

Üye değişkenleri, sınıf yavaşlamasında bildirildikleri sıraya göre ilklendirilir, böylece aşağıdakileri yapabilirsiniz (matematiksel olarak)

#include <iostream>
int gcd(int a, int b){return 2;}; // Greatest Common Divisor of (4, 6) just to test
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator{a/gcd(a,b)}, denominator(b/(a/numerator))
    {

    }
//private:
    const int numerator, denominator;//make sure that they are in this order
};
//Test
int main(){
    Fraction f{4,6};
    std::cout << f.numerator << " / " << f.denominator;
}

Başka bir inşaatçı çağırmaya ve hatta onları yapmaya gerek yok.


6
Tamam, özellikle GCD için çalışır, ancak diğer birçok kullanım durumu muhtemelen 2. sabitleri argümanlardan ve ilkinden elde edemez. Ve yazıldığı gibi, bu, derleyicinin optimize edemeyeceği ideal ve diğer bir dezavantaj olan ekstra bir bölüme sahiptir. GCD yalnızca bir bölüme mal olabilir, bu da GCD'yi iki kez aramak kadar kötü olabilir. (Bölmenin, modern CPU'larda olduğu gibi diğer işlemlerin maliyetine baskın olduğunu varsayarsak.)
Peter Cordes

@PeterCordes ama diğer çözüm ekstra bir işlev çağrısı vardır ve daha fazla komut belleği ayırır.
asmmo

1
Drew'un temsilci yapıcısından mı bahsediyorsun? Bu açıkça Fraction(a,b,gcd(a,b))delegasyonun arayan kişiyi sıralayabilir ve daha az toplam maliyete yol açabilir. Bu satır içi işlemin, derleyicinin yapması, buradaki fazladan bölünmeyi geri almaktan daha kolaydır. Ben bunu deneyin vermedi godbolt.org ama meraklı iseniz o zaman. -O3Normal bir derlemenin kullandığı gibi gcc veya clang kullanın. (C ++, modern bir optimize edici derleyicinin varsayımı etrafında tasarlanmıştır, bu nedenle gibi özellikler constexpr)
Peter Cordes

-3

@Drew Dormann, aklımdakilere benzer bir çözüm verdi. OP asla ktoru değiştiremediğinden bahsetmediğinden, bu ile çağrılabilir Fraction f {a, b, gcd(a, b)}:

Fraction(int a, int b, int tmp): numerator {a/tmp}, denominator {b/tmp}
{
}

Sadece bu şekilde bir işleve, kurucuya veya başka bir şekilde ikinci bir çağrı olmaz, bu yüzden zaman kaybı olmaz. Zaten bir geçici bellek yaratılması gerektiğinden, boşa harcanan bir şey yok, bu yüzden onu da iyi kullanabilirsiniz. Ayrıca fazladan bir bölünmeyi de önler.


3
Düzenlemeniz soruyu bile yanıtlamıyor. Şimdi arayanın 3. arg'u geçmesini mi istiyorsunuz? Oluşturucu gövdesi içindeki atamayı kullanan orijinal sürümünüz işe yaramaz const, en azından diğer türler için çalışır. Ve hangi ekstra bölünmeden kaçınıyorsunuz? Yani asmmo'nun cevabı mı?
Peter Cordes

1
Tamam, nedenini açıkladığına göre aşağı notumu kaldır. Ancak bu oldukça korkunç görünüyor ve yapıcı işlerinden bazılarını manuel olarak her arayana satır içine almanızı gerektiriyor. Bu, DRY'nin (kendinizi tekrar etmeyin) ve sınıfın sorumluluğunun / içsellerinin kapsüllenmesinin tersidir. Çoğu insan bunu kabul edilebilir bir çözüm olarak görmez. Bunu temiz bir şekilde yapmak için bir C ++ 11 yolu olduğu göz önüne alındığında, belki de eski bir C ++ sürümüyle sıkışıp kalmadıkça ve sınıfın bu yapıcıya çok az çağrısı olmadığı sürece bunu kimse yapmamalıdır.
Peter Cordes

2
@aconcernedcitizen: Performans nedenlerinden bahsetmiyorum, kod kalitesi nedenlerinden bahsediyorum. Yolunuzla, bu sınıfın dahili olarak çalışma şeklini değiştirdiyseniz, kurucuya yapılan tüm çağrıları bulup bu 3. arg'u değiştirmeniz gerekir. Bu ekstra ,gcd(foo, bar), kaynaktaki her çağrı sitesinden dışarıda bırakılabilecek ve bu nedenle hesaba katılması gereken ekstra koddur . Bu performans değil, sürdürülebilirlik / okunabilirlik sorunudur. Derleyici, büyük olasılıkla performans için istediğiniz derleme zamanında satır içine alacaktır.
Peter Cordes

1
@PeterCordes Haklısın, şimdi zihnimin çözüme sabitlendiğini görüyorum ve diğer her şeyi göz ardı ettim. Her iki durumda da, cevap sadece shaming için kalır. Bununla ilgili şüphelerim olduğunda, nereye bakacağımı bileceğim.
ilgili bir vatandaş

1
Ayrıca Fraction f( x+y, a+b ); istediğiniz şekilde yazmak için, BadFraction f( x+y, a+b, gcd(x+y, a+b) );tmp vars yazmanız veya kullanmanız gerekir . Ya da daha da kötüsü, yazmak istiyorsanız Fraction f( foo(x), bar(y) );- o zaman geri dönüş değerlerini tutmak için bazı tmp değişkenleri beyan etmek için çağrı sitesine ihtiyacınız olacak veya bu işlevleri tekrar çağıracak ve derleyici CSE'leri onları uzakta umuyoruz, kaçındığımız şey budur. Bir çağıranın argümanları karıştırması durumunda hata ayıklamak istiyor musunuz gcd? Hayır? O zaman bu hatayı mümkün kılma.
Peter Cordes
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.