กฎข้อที่ 5 - จะใช้หรือเปล่า?


20

กฎ 3 ( กฎ 5ในมาตรฐาน c ++ ใหม่):

หากคุณต้องการประกาศตัว Destructor ให้คัดลอก Constructor หรือตัวดำเนินการกำหนดค่าด้วยตนเองอย่างชัดเจนคุณอาจต้องประกาศทั้งสามอย่างชัดเจน

แต่ในทางกลับกัน " รหัสสะอาด " ของ Martin แนะนำให้ลบตัวสร้างและตัวทำลายที่ว่างเปล่าทั้งหมด (หน้า 293, G12: ความยุ่งเหยิง ):

สิ่งที่ใช้เป็นตัวสร้างเริ่มต้นโดยไม่มีการใช้งาน? สิ่งที่มันทำคือถ่วงโค้ดด้วยส่วนที่ไม่มีความหมาย

ดังนั้นวิธีจัดการกับทั้งสองความคิดเห็นที่ตรงกันข้าม? ควรใช้ตัวสร้าง / ตัวทำลายที่ว่างเปล่าจริงหรือไม่?


ตัวอย่างถัดไปแสดงให้เห็นถึงสิ่งที่ฉันหมายถึง:

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

รวบรวมการปรับโดยใช้ g ++ 4.6.1 ด้วย:

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

destructor สำหรับstruct Aว่างเปล่าและไม่จำเป็นจริงๆ ดังนั้นควรอยู่ที่นั่นหรือควรลบออก


15
คำพูดทั้งสองพูดถึงสิ่งต่าง ๆ หรือฉันพลาดจุดของคุณโดยสิ้นเชิง
Benjamin Bannier

1
@ honk ในมาตรฐานการเขียนโค้ดของทีมของเราเรามีกฎที่จะประกาศทั้ง 4 (constructor, destructor, copy constructors) เสมอ ฉันสงสัยว่ามันสมเหตุสมผลหรือไม่ที่จะทำ ฉันต้องประกาศ destructors เสมอแม้ว่าจะว่างเปล่าหรือไม่?
BЈовић

ในฐานะที่เป็นที่ว่างเปล่า desctructors คิดเกี่ยวกับเรื่องนี้: codesynthesis.com/~boris/blog/2012/04/04/... มิฉะนั้นกฎของ 3 (5) ก็สมเหตุสมผลดีสำหรับฉันไม่มีความคิดว่าทำไมเราถึงต้องการกฎข้อที่ 4
Benjamin Bannier

@ honk ระวังเกี่ยวกับข้อมูลที่คุณค้นหาในเน็ต ไม่จริงทั้งหมด ตัวอย่างเช่นvirtual ~base () = default;ไม่คอมไพล์ (ด้วยเหตุผลที่ดี)
BЈовић

@VJovic ไม่ว่าคุณไม่จำเป็นต้องประกาศ destructor ที่ว่างเปล่าเว้นเสียแต่ว่าคุณจะต้องทำให้มันเป็นเสมือน และในขณะที่เราอยู่ในหัวข้อคุณไม่ควรใช้auto_ptrอย่างใดอย่างหนึ่ง
Dima

คำตอบ:


44

สำหรับการเริ่มต้นกฎบอกว่า "อาจจะ" ดังนั้นจึงใช้ไม่ได้เสมอไป

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

ดังนั้นโดยสรุปคุณไม่ควรประกาศตัวสร้างเปล่าหรือ destructors ที่ว่างเปล่า แต่เป็นไปได้มากว่าหากต้องการสิ่งใดสิ่งหนึ่งก็จำเป็นเช่นกัน

สำหรับตัวอย่างของคุณ: ในกรณีเช่นนี้คุณสามารถปล่อย destructor ออกได้ เห็นได้ชัดว่ามันไม่ทำอะไรเลย การใช้สมาร์ทพอยน์เตอร์เป็นตัวอย่างที่สมบูรณ์แบบในเรื่องที่และสาเหตุที่กฎ 3 ข้อไม่มีอยู่

มันเป็นเพียงแนวทางสำหรับการดูรหัสของคุณในกรณีที่คุณลืมใช้ฟังก์ชันการทำงานที่สำคัญที่คุณอาจพลาดไป


ด้วยการใช้ตัวชี้สมาร์ท destructors ว่างเปล่าในกรณีส่วนใหญ่ (ฉันพูด> 99% ของ destructors ในฐานรหัสของฉันว่างเปล่าเพราะเกือบทุกชั้นใช้ pimpl idiom)
BЈовић

ว้าววววววว ด้วยคอมไพเลอร์หลาย ๆ ตัวจะทำให้ยากขึ้นในการเพิ่มประสิทธิภาพ (เช่นยากที่จะอินไลน์)
Benjamin Bannier

@honk คุณหมายถึงอะไรโดย "compilers หลายคนสิว" :)
BЈовић

@VJovic: ขอโทษพิมพ์ผิด: 'รหัส
pimpled

4

ไม่มีข้อโต้แย้งจริงๆที่นี่ กฎข้อที่ 3 พูดถึง destructor ตัวสร้างการคัดลอกและตัวดำเนินการกำหนดค่าการคัดลอก ลุงบ๊อบพูดถึงตัวสร้างปริยายที่ว่างเปล่า

หากคุณต้องการ destructor คลาสของคุณอาจมีตัวชี้ไปยังหน่วยความจำที่จัดสรรแบบไดนามิกและคุณอาจต้องการสำเนา ctor และตัวoperator=()ที่ทำสำเนาลึก นี่คือมุมฉากอย่างสมบูรณ์ว่าคุณต้องการนวกรรมิกเริ่มต้นหรือไม่

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

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


2

ที่นี่ความเป็นไปได้ของคุณ (*) เทียบเท่ากับค่าเริ่มต้นหนึ่งคอนสตรัคเตอร์ / การมอบหมาย / destructor มีวัตถุประสงค์: บันทึกความจริงที่คุณมีเกี่ยวกับปัญหาและพิจารณาว่าพฤติกรรมเริ่มต้นนั้นถูกต้อง BTW ใน C ++ 11 สิ่งต่าง ๆ ไม่เสถียรพอที่จะรู้ว่า=defaultสามารถตอบสนองวัตถุประสงค์นั้นได้หรือไม่

(มีวัตถุประสงค์ที่เป็นไปได้อื่น: ให้คำจำกัดความของบรรทัดแทนที่จะเป็นค่าอินไลน์ที่เป็นค่าเริ่มต้นดีกว่าที่จะจัดทำเอกสารอย่างชัดเจนหากคุณมีเหตุผลที่จะทำเช่นนั้น)

(*) ความเป็นไปได้เพราะฉันจำกรณีในชีวิตจริงที่ไม่ใช้กฏสามข้อได้ถ้าฉันต้องทำอะไรบางอย่างในที่เดียวฉันต้องทำบางสิ่งในที่อื่น


แก้ไขหลังจากเพิ่มตัวอย่าง ตัวอย่างของคุณที่ใช้ auto_ptr นั้นน่าสนใจ คุณกำลังใช้ตัวชี้สมาร์ท แต่ไม่ใช่ตัวชี้ที่ขึ้นอยู่กับงาน ฉันควรจะเขียนอย่างใดอย่างหนึ่งซึ่ง - โดยเฉพาะถ้าสถานการณ์เกิดขึ้นบ่อย - กว่าทำสิ่งที่คุณทำ (ถ้าฉันไม่เข้าใจผิดก็ไม่ได้มาตรฐานหรือการเพิ่มพลังให้อย่างใดอย่างหนึ่ง)


ตัวอย่างแสดงให้เห็นถึงจุดของฉัน destructor นั้นไม่จำเป็นจริงๆ แต่กฎของ 3 บอกว่าควรจะอยู่ตรงนั้น
BЈовић

1

กฎ 5 เป็นส่วนเสริมของกฎ 3 ซึ่งเป็นพฤติกรรมแบบ cautelative againt วัตถุที่เป็นไปได้ในทางที่ผิด

หากคุณจำเป็นต้องมี destructor ก็หมายความว่าคุณได้ "การจัดการทรัพยากร" นอกเหนือจากค่าเริ่มต้น (เพียงแค่สร้างและทำลายค่า )

ตั้งแต่การคัดลอกกำหนดย้ายและการถ่ายโอนโดยการคัดลอกค่าเริ่มต้นค่าถ้าคุณไม่ได้ถือเพียงค่าคุณต้องกำหนดว่าจะทำอย่างไร

ที่กล่าวว่า C ++ ลบสำเนาถ้าคุณกำหนดการย้ายและลบการย้ายถ้าคุณกำหนดสำเนา ในกรณีส่วนใหญ่คุณต้องกำหนดถ้าคุณต้องการเลียนแบบค่า (ดังนั้นคัดลอก mut โคลนทรัพยากรและการย้ายไม่มีความรู้สึก) หรือผู้จัดการทรัพยากร (และด้วยเหตุนี้ย้ายทรัพยากรที่คัดลอกไม่มีเหตุผล: กฎ ของ 3 กลายเป็นกฎของอีก 3 )

กรณีที่คุณต้องกำหนดทั้งการคัดลอกและย้าย (กฎ 5) ค่อนข้างหายาก: โดยทั่วไปคุณจะต้องมี "ค่าตัวใหญ่" ที่จะต้องคัดลอกหากมอบให้กับวัตถุที่แตกต่างกัน แต่สามารถเคลื่อนย้ายได้หากนำมาจากวัตถุชั่วคราวโคลนแล้วทำลาย ) นั่นเป็นกรณีสำหรับคอนเทนเนอร์ STL หรือคอนเทนเนอร์เลขคณิต

กรณีที่สามารถ matrixes: พวกเขาจะต้องสำเนาการสนับสนุนเพราะพวกเขามีค่านิยม ( a=b; c=b; a*=2; b*=3;ไม่ต้องมีอิทธิพลต่อกันและกัน) แต่พวกเขาสามารถเพิ่มประสิทธิภาพโดยการสนับสนุนนอกจากนี้ยังมีการเคลื่อนย้าย ( a = 3*b+4*cมี+ที่จะใช้เวลาสองชั่วคราวและสร้างชั่วคราว: หลีกเลี่ยงการโคลนและลบสามารถ ที่มีประโยชน์)


1

ฉันชอบถ้อยคำที่แตกต่างกันของกฎสามข้อซึ่งดูสมเหตุสมผลกว่าซึ่งก็คือ "ถ้าคลาสของคุณต้องการ destructor (นอกเหนือจาก virtual destructor ที่ว่างเปล่า) มันอาจต้องใช้ตัวสร้างสำเนาและตัวดำเนินการกำหนด"

การระบุว่าเป็นความสัมพันธ์แบบทางเดียวจาก destructor ทำให้บางสิ่งชัดเจนขึ้น:

  1. ไม่ได้ใช้ในกรณีที่คุณให้ตัวสร้างสำเนาที่ไม่ใช่ค่าเริ่มต้นหรือตัวดำเนินการกำหนดค่าเป็นการเพิ่มประสิทธิภาพเท่านั้น

  2. เหตุผลสำหรับกฎนี้คือตัวสร้างสำเนาเริ่มต้นหรือผู้ดำเนินการที่ได้รับมอบหมายสามารถจัดการทรัพยากรด้วยตนเองได้ หากคุณจัดการทรัพยากรด้วยตนเองคุณมีแนวโน้มที่จะรู้ว่าคุณจะต้องมี destructor เพื่อปล่อย


-3

ยังมีอีกประเด็นที่ยังไม่ได้กล่าวถึงในการอภิปราย: ผู้ทำลายระบบควรเป็นเสมือน

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

ตัวสร้างจะต้องประกาศเป็นเสมือนในคลาสพื้นฐานเพื่อให้เสมือนในคลาสที่ได้รับทั้งหมดด้วย ดังนั้นแม้ว่าคลาสพื้นฐานของคุณไม่จำเป็นต้องมีตัวทำลายระบบ แต่คุณก็ต้องประกาศและใช้ตัวทำลายระบบว่างเปล่า

หากคุณใส่คำเตือนทั้งหมดใน (-Wall -Wextra -Weffc ++) g ++ จะเตือนคุณเกี่ยวกับสิ่งนี้ ฉันคิดว่ามันเป็นแนวปฏิบัติที่ดีที่จะประกาศตัวทำลายเสมือนในคลาสใด ๆ เสมอเพราะคุณไม่เคยรู้เลยว่าถ้าชั้นเรียนของคุณจะกลายเป็นคลาสพื้นฐาน หากไม่ต้องการ destructor เสมือนจริงก็ไม่เป็นอันตราย ถ้าเป็นเช่นนั้นคุณประหยัดเวลาในการค้นหาข้อผิดพลาด


1
แต่ฉันไม่ต้องการนวกรรมิกเสมือน ถ้าฉันทำอย่างนั้นทุกครั้งที่มีการเรียกใช้เมธอดใด ๆ ก็จะใช้การจัดส่งเสมือน btw รับทราบว่าไม่มีสิ่งเช่น "ตัวสร้างเสมือน" ใน c ++ นอกจากนี้ฉันรวบรวมตัวอย่างเป็นระดับการเตือนที่สูงมาก
BЈовић

IIRC กฎที่ gcc ใช้สำหรับคำเตือนและกฎที่ฉันปฏิบัติตามโดยทั่วไปก็คือควรมีตัวทำลายเสมือนหากมีวิธีเสมือนอื่น ๆ ในชั้นเรียน
จูลส์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.