เริ่มต้นสมาชิกคลาสคงที่หลายคนโดยใช้การเรียกฟังก์ชันหนึ่ง C ++


50

หากฉันมีตัวแปรสมาชิกคงที่แตกต่างกันสองตัวซึ่งทั้งสองต้องเริ่มต้นใหม่โดยใช้การเรียกฟังก์ชันเดียวกันมีวิธีการทำเช่นนี้โดยไม่ต้องเรียกใช้ฟังก์ชันสองครั้งหรือไม่

ตัวอย่างเช่นคลาสเศษส่วนที่ตัวเศษและตัวหารเป็นค่าคงที่

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;
};

ส่งผลให้เสียเวลาเนื่องจากฟังก์ชั่น GCD ถูกเรียกสองครั้ง คุณสามารถกำหนดสมาชิกคลาสใหม่gcd_a_bและก่อนกำหนดเอาต์พุตของ gcd ให้กับรายการนั้นในรายการ initializer แต่สิ่งนี้จะนำไปสู่การสูญเสียความจำ

โดยทั่วไปแล้วมีวิธีการทำเช่นนี้โดยไม่ต้องใช้ฟังก์ชั่นการโทรหรือหน่วยความจำที่สูญเปล่า คุณสามารถสร้างตัวแปรชั่วคราวในรายการ initializer ได้หรือไม่? ขอบคุณ.


5
คุณมีข้อพิสูจน์ว่า "ฟังก์ชัน GCD ถูกเรียกสองครั้ง" หรือไม่? มันถูกกล่าวถึงสองครั้ง แต่นั่นไม่ใช่สิ่งเดียวกันกับโค้ดการคอมไพเลอร์ที่เรียกมันสองครั้ง คอมไพเลอร์อาจอนุมานได้ว่ามันเป็นฟังก์ชั่นที่บริสุทธิ์และนำคุณค่าของมันมาใช้ใหม่ในการกล่าวถึงครั้งที่สอง
Eric Towers

6
@EricTowers: ใช่บางครั้งคอมไพเลอร์สามารถแก้ไขปัญหาในทางปฏิบัติสำหรับบางกรณี แต่ถ้าพวกเขาสามารถเห็นคำจำกัดความ (หรือคำอธิบายประกอบบางอย่างในวัตถุ) มิฉะนั้นไม่มีวิธีที่จะพิสูจน์ได้ว่ามันบริสุทธิ์ คุณควรรวบรวมเมื่อเปิดใช้งานการเพิ่มประสิทธิภาพเวลาเชื่อมโยง แต่ไม่ใช่ทุกคนที่ทำ และฟังก์ชั่นอาจอยู่ในห้องสมุด หรือพิจารณากรณีของฟังก์ชั่นที่ที่จะมีผลข้างเคียงและเรียกมันว่าครั้งว่าเป็นเรื่องของความถูกต้องหรือไม่?
Peter Cordes

@EricTowers จุดที่น่าสนใจ จริง ๆ แล้วฉันพยายามตรวจสอบโดยใส่คำสั่งการพิมพ์ไว้ในฟังก์ชั่น GCD แต่ตอนนี้ฉันรู้แล้วว่ามันจะป้องกันไม่ให้มันเป็นฟังก์ชั่นที่บริสุทธิ์
Qq0

@ Qq0: คุณสามารถตรวจสอบได้โดยดูที่คอมไพเลอร์สร้าง asm เช่นใช้คอมไพเลอร์สำรวจ Godboltกับ GCC -O3หรือเสียงดังกราว แต่อาจเป็นไปได้สำหรับการทดสอบการใช้งานง่าย ๆ จริง ๆ แล้วมันจะอินไลน์เรียกฟังก์ชัน หากคุณใช้__attribute__((const))หรือบริสุทธิ์บนต้นแบบโดยไม่ต้องให้คำจำกัดความที่มองเห็นได้มันควรปล่อยให้ GCC หรือเสียงดังกราวทำการกำจัด subexpression ทั่วไป (CSE) ระหว่างการเรียกสองครั้งด้วย ARG เดียวกัน โปรดทราบว่าคำตอบของ Drew นั้นใช้ได้กับฟังก์ชั่นที่ไม่บริสุทธิ์ดังนั้นจึงดีกว่ามากและคุณควรใช้ฟังก์ชั่นเมื่อ func อาจไม่อินไลน์
Peter Cordes

โดยทั่วไปแล้วตัวแปรสมาชิก const แบบไม่คงที่จะหลีกเลี่ยงได้ดีที่สุด หนึ่งในไม่กี่พื้นที่ที่ทุกอย่างใช้ไม่ได้บ่อย ตัวอย่างเช่นคุณไม่สามารถกำหนดวัตถุคลาสได้ คุณสามารถ emplace_back เป็นเวกเตอร์ แต่ตราบใดที่ขีดจำกัดความจุไม่ได้ถูกปรับขนาด
doug

คำตอบ:


66

โดยทั่วไปแล้วมีวิธีการทำเช่นนี้โดยไม่ต้องใช้ฟังก์ชั่นการโทรหรือหน่วยความจำที่สูญเปล่า

ใช่. สิ่งนี้สามารถทำได้ด้วยตัวสร้างการมอบหมายที่แนะนำใน C ++ 11

ตัวสร้างการมอบหมายเป็นวิธีที่มีประสิทธิภาพมากในการรับค่าชั่วคราวที่จำเป็นสำหรับการก่อสร้างก่อนที่ตัวแปรสมาชิกใดจะเริ่มต้น

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;
};

จากค่าใช้จ่ายผู้ผลิตรายอื่นจะเรียกค่าก่อสร้างสำคัญหรือไม่?
Qq0

1
@ Qq0 คุณสามารถสังเกตได้ที่นี่ว่าไม่มีค่าใช้จ่ายเมื่อเปิดใช้งานการปรับให้เหมาะสมที่สุด
Drew Dormann

2
@ Qq0: C ++ ออกแบบมาเพื่อคอมไพเลอร์การปรับให้เหมาะสมที่ทันสมัย พวกเขาสามารถ inline เล็กน้อยคณะผู้แทนนี้โดยเฉพาะอย่างยิ่งถ้าคุณทำให้มันปรากฏในคำจำกัดความของคลาส (ใน.h) แม้ว่าคำนิยามตัวสร้างที่แท้จริงไม่สามารถมองเห็นได้สำหรับการทำอินไลน์ คือgcd()การโทรจะ inline ในแต่ละตัวสร้าง callsite และปล่อยให้เป็นเพียงแค่callไป 3 ตัวถูกดำเนินการคอนสตรัคส่วนตัว
Peter Cordes

10

สมาชิก vars จะเริ่มต้นตามลำดับที่ประกาศไว้ในคลาส decleration ดังนั้นคุณสามารถทำสิ่งต่อไปนี้ได้ (ทางคณิตศาสตร์)

#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;
}

ไม่จำเป็นต้องเรียกสิ่งก่อสร้างอื่นหรือทำให้พวกเขา


6
ตกลงที่ใช้งานได้กับ GCD โดยเฉพาะ แต่กรณีการใช้งานอื่น ๆ อีกมากมายอาจไม่สามารถหาค่า const ที่ 2 จาก args และตัวแรกได้ และตามที่เขียนไว้นี้มีการแบ่งพิเศษอีกหนึ่งส่วนซึ่งเป็นข้อเสียอีกประการหนึ่งเมื่อเทียบกับอุดมคติที่คอมไพเลอร์อาจไม่เหมาะสม GCD อาจมีค่าใช้จ่ายเพียงส่วนเดียวดังนั้นการดำเนินการนี้อาจไม่ดีเท่าการเรียก GCD สองครั้ง (สมมติว่าแผนกนั้นเป็นผู้ควบคุมค่าใช้จ่ายในการดำเนินการอื่น ๆ เช่นเดียวกับที่ใช้กับซีพียูรุ่นใหม่)
Peter Cordes

@PeterCordes แต่โซลูชันอื่นมีการเรียกใช้ฟังก์ชันเพิ่มเติมและจัดสรรหน่วยความจำคำสั่งเพิ่มเติม
asmmo

1
คุณกำลังพูดถึง Constructor ของ Drew หรือไม่? เห็นได้ชัดว่าสามารถรวมFraction(a,b,gcd(a,b))ตัวแทนเข้าสู่ผู้โทรซึ่งนำไปสู่ค่าใช้จ่ายทั้งหมดน้อยลง การเรียบเรียงนั้นง่ายกว่าสำหรับคอมไพเลอร์ที่ต้องทำมากกว่าเลิกทำการแบ่งส่วนพิเศษในส่วนนี้ ฉันไม่ได้ลองในgodbolt.orgแต่คุณทำได้ถ้าคุณอยากรู้ ใช้ gcc หรือเสียงดังกราวด์-O3เหมือนรุ่นปกติจะใช้ (C ++ ได้รับการออกแบบรอบ ๆ สมมติฐานของคอมไพเลอร์การปรับให้เหมาะสมที่ทันสมัยดังนั้นคุณสมบัติเช่นนี้constexpr)
Peter Cordes

-3

@Drew Dormann ให้ทางออกคล้ายกับสิ่งที่ฉันมีในใจ เนื่องจาก OP ไม่เคยกล่าวถึงไม่สามารถแก้ไข ctor ได้จึงสามารถเรียกใช้ด้วยFraction f {a, b, gcd(a, b)}:

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

ด้วยวิธีนี้จะไม่มีการเรียกฟังก์ชันที่สองตัวสร้างหรืออื่น ๆ ดังนั้นจึงไม่เสียเวลา และมันก็ไม่ได้สูญเปล่าไปเพราะหน่วยความจำชั่วคราวจะต้องสร้างต่อไปดังนั้นคุณอาจจะใช้มันให้เป็นประโยชน์ นอกจากนี้ยังหลีกเลี่ยงการแบ่งพิเศษ


3
การแก้ไขของคุณทำให้ไม่ได้ตอบคำถาม ตอนนี้คุณต้องการให้ผู้โทรผ่าน ARG ที่ 3? เวอร์ชันดั้งเดิมของคุณที่ใช้การมอบหมายภายในเนื้อความคอนสตรัคเตอร์ไม่ทำงานconstแต่อย่างน้อยก็ใช้ได้กับประเภทอื่น ๆ และคุณแบ่งส่วนอะไรเป็นพิเศษเช่นกัน คุณหมายถึงคำตอบของ vs. asmmo ใช่ไหม
Peter Cordes

1
ตกลงลบคะแนนโหวตของฉันทันทีที่คุณได้อธิบายประเด็นของคุณ แต่ดูเหมือนว่ามันจะแย่มาก ๆ และคุณต้องใช้อินไลน์ของผู้สร้างในการโทรทุกคน สิ่งนี้ตรงกันข้ามกับ DRY (อย่าพูดซ้ำตัวเอง) และการห่อหุ้มความรับผิดชอบ / การฝึกปฏิบัติภายใน คนส่วนใหญ่จะไม่คิดว่านี่เป็นทางออกที่ยอมรับได้ ระบุว่ามีวิธี C ++ 11 ที่จะทำสิ่งนี้อย่างหมดจดไม่มีใครควรทำเช่นนี้เว้นแต่ว่าพวกเขาจะติดอยู่กับรุ่น C ++ ที่เก่ากว่าและชั้นเรียนมีการเรียกคอนสตรัคเตอร์นี้น้อยมาก
Peter Cordes

2
@aconcernedcitizen: ฉันไม่ได้หมายถึงเหตุผลด้านประสิทธิภาพ แต่ฉันหมายถึงเหตุผลด้านคุณภาพของรหัส ด้วยวิธีการของคุณถ้าคุณเคยเปลี่ยนวิธีการเรียนนี้ทำงานภายในคุณจะต้องไปหาสายทั้งหมดไปที่ตัวสร้างและเปลี่ยน ARG ที่ 3 พิเศษที่,gcd(foo, bar)เป็นรหัสพิเศษที่สามารถและดังนั้นจึงควรจะเอาเรื่องของทุก callsite ในแหล่งที่มา นั่นเป็นปัญหาการบำรุงรักษา / การอ่านไม่ใช่ประสิทธิภาพ คอมไพเลอร์ส่วนใหญ่จะอินไลน์ในเวลารวบรวมซึ่งคุณต้องการประสิทธิภาพ
Peter Cordes

1
@ PeterCordes คุณถูกต้องตอนนี้ฉันเห็นว่าใจของฉันได้รับการแก้ไขในการแก้ปัญหาและฉันไม่สนใจทุกอย่างอื่น ไม่ว่าจะด้วยวิธีใดคำตอบจะยังคงอยู่เพียงเพื่อความอับอาย เมื่อใดก็ตามที่ฉันมีข้อสงสัยเกี่ยวกับมันฉันจะรู้ว่าจะหาที่ไหน
พลเมืองที่เกี่ยวข้อง

1
พิจารณากรณีของFraction f( x+y, a+b ); การเขียนด้วยวิธีของคุณคุณจะต้องเขียนBadFraction f( x+y, a+b, gcd(x+y, a+b) );หรือใช้ vmp tars หรือแย่กว่านั้นถ้าคุณต้องการเขียนFraction f( foo(x), bar(y) );- คุณต้องให้ไซต์การโทรประกาศ tmp vars เพื่อเก็บค่าส่งคืนหรือเรียกใช้ฟังก์ชันเหล่านั้นอีกครั้งและหวังว่า CSE ของคอมไพเลอร์จะหายไปซึ่งเป็นสิ่งที่เราหลีกเลี่ยง คุณต้องการที่จะแก้ปัญหากรณีของผู้โทรหนึ่งคนผสม args เข้าด้วยกันหรือไม่gcdดังนั้นจริง ๆ แล้ว GCD ของ 2 args แรกผ่านไปยังตัวสร้างหรือไม่ ไม่มี? จากนั้นอย่าทำให้ข้อบกพร่องนั้นเป็นไปได้
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.