เหตุใด destructor ไม่ถูกเรียกในตัวดำเนินการลบ


16

ฉันพยายามโทร::deleteหาชั้นเรียนที่อยู่ในoperator deleteนั้น แต่ตัวทำลายจะไม่ถูกเรียก

ฉันกำหนดคลาสMyClassที่operator deleteมีการโหลดมากเกินไป ทั่วโลกoperator deleteก็มีมากเกินไป มากเกินไปoperator deleteของจะโทรมากเกินไปทั่วโลกMyClassoperator delete

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

ผลลัพธ์คือ:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

จริง:

มีเพียงหนึ่งเรียกร้องให้ destructor คือก่อนที่จะโทรมากเกินไปของoperator deleteMyClass

ที่คาดว่าจะ

มีการเรียกไปยัง destructor สองสาย หนึ่งก่อนที่จะโทรมากเกินไปของoperator delete ก่อนที่จะเรียกอีกทั่วโลกMyClassoperator delete


6
MyClass::operator new()ควรจัดสรรหน่วยความจำดิบจำนวนsizeไบต์(อย่างน้อย) MyClassมันไม่ควรพยายามที่จะสมบูรณ์สร้างตัวอย่างของ ตัวสร้างของจะถูกดำเนินการหลังจากที่MyClass MyClass::operator new()จากนั้นdeleteนิพจน์ในการmain()เรียก destructor และปล่อยหน่วยความจำ (โดยไม่เรียกตัว destructor อีกครั้ง) ::delete pแสดงออกมีข้อมูลเกี่ยวกับชนิดของวัตถุที่ไม่มีpจุดเนื่องจากpเป็นvoid *จึงไม่สามารถเรียกใช้เตาเผา
ปีเตอร์

ที่เกี่ยวข้อง: stackoverflow.com/a/8918942/845092
Mooing Duck

2
คำตอบที่คุณได้รับนั้นถูกต้องแล้ว แต่ฉันสงสัยว่า: ทำไมคุณพยายามแทนที่ใหม่และลบ กรณีใช้งานทั่วไปกำลังใช้การจัดการหน่วยความจำที่กำหนดเอง (GC, หน่วยความจำที่ไม่ได้มาจาก malloc เริ่มต้น () ฯลฯ ) บางทีคุณอาจกำลังใช้เครื่องมือที่ผิดสำหรับสิ่งที่คุณพยายามจะทำ
noamtm

2
::delete p;ทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดเนื่องจากประเภทของ*pไม่เหมือนกับชนิดของวัตถุที่ถูกลบ (หรือคลาสพื้นฐานที่มีตัวทำลายเสมือน)
MM

@MM คอมไพเลอร์รายใหญ่จะเตือนเพียงอย่างเดียวเท่านั้นดังนั้นฉันจึงไม่ทราบว่าvoid*ตัวถูกดำเนินการแม้จะมีรูปแบบไม่ชัดเจน [expr.delete] / 1 : " ตัวถูกดำเนินการจะเป็นตัวชี้ไปยังชนิดของวัตถุหรือประเภทของคลาส [... ] นี่หมายความว่าวัตถุไม่สามารถลบได้โดยใช้ตัวชี้เป็นโมฆะประเภทเพราะเป็นโมฆะไม่ได้เป็นประเภทวัตถุ * "@OP ฉันได้แก้ไขคำตอบแล้ว
วอลนั

คำตอบ:


17

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

รุ่นทั่วโลกของฟังก์ชั่นเหล่านี้เป็นและ ::operator new และเป็นนิพจน์ / ลบ - นิพจน์เช่นเดียวกับ/ แตกต่างจากสิ่งนั้นในนั้นและจะข้ามคลาสเฉพาะ/ โอเวอร์โหลด::operator delete::new::deletenewdelete::new::deleteoperator newoperator delete

ใหม่ / ลบการแสดงออกสร้าง / ทำลายและจัดสรร / deallocate (โดยการเรียกที่เหมาะสมoperator newหรือoperator deleteก่อนการก่อสร้างหรือหลังการทำลาย)

เนื่องจากเกินของคุณเป็นเพียงส่วนหนึ่งที่รับผิดชอบในการจัดสรร / deallocation ก็ควรจะเรียก::operator newและ::operator deleteแทนและ::new::delete

deleteในdelete myClass;เป็นผู้รับผิดชอบสำหรับการโทรเตาเผา

::delete p;ไม่เรียก destructor เนื่องจากpมีชนิดvoid*ดังนั้นนิพจน์ไม่สามารถทราบว่า destructor ที่จะเรียก มันอาจจะโทรหาคุณแทนที่::operator deleteเพื่อยกเลิกการจัดสรรหน่วยความจำถึงแม้ว่าการใช้void*ตัวถูกดำเนินการเพื่อลบ - นิพจน์เป็นรูปแบบไม่ดี (ดูแก้ไขด้านล่าง)

::new MyClass();เรียกการแทนที่ของคุณ::operator newเพื่อจัดสรรหน่วยความจำและสร้างวัตถุในนั้น ตัวชี้ไปยังวัตถุนี้ถูกส่งคืนเป็นvoid*นิพจน์ใหม่MyClass* myClass = new MyClass();ซึ่งจะสร้างวัตถุอื่นในหน่วยความจำนี้ซึ่งจะสิ้นสุดอายุการใช้งานของวัตถุก่อนหน้าโดยไม่เรียก destructor


แก้ไข:

ต้องขอบคุณความเห็นของ @ MM เกี่ยวกับคำถามนี้ฉันจึงตระหนักว่าvoid*ตัวถูกดำเนินการ::deleteเป็นจริง ( [expr.delete] / 1 ) อย่างไรก็ตามคอมไพเลอร์รายใหญ่ดูเหมือนจะตัดสินใจที่จะเตือนเรื่องนี้เท่านั้นไม่ใช่ข้อผิดพลาด ก่อนที่มันจะถูกสร้างขึ้นมาไม่ดีที่เกิดขึ้นโดยใช้::deleteบนvoid*มีพฤติกรรมที่ไม่ได้กำหนดไว้แล้วดูคำถามนี้

ดังนั้นโปรแกรมของคุณจึงมีรูปแบบไม่ดีและคุณไม่มีการรับประกันใด ๆ ว่ารหัสนั้นทำในสิ่งที่ฉันได้อธิบายไว้ข้างต้นถ้ามันยังสามารถรวบรวมได้


@SanderDeDycker ชี้ให้เห็นด้านล่างคุณยังมีพฤติกรรมที่ไม่ได้กำหนดเนื่องจากการสร้างวัตถุอื่นในหน่วยความจำที่มีMyClassวัตถุอยู่แล้วโดยไม่เรียกตัวทำลายวัตถุของวัตถุนั้นก่อนคุณกำลังละเมิด[basic.life] / 5ซึ่งห้ามทำเช่นนั้นหาก โปรแกรมขึ้นอยู่กับผลข้างเคียงของ destructor ในกรณีนี้printfคำสั่งใน destructor นั้นมีผลข้างเคียง


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

13

การโอเวอร์โหลดเฉพาะคลาสของคุณทำไม่ถูกต้อง สิ่งนี้สามารถเห็นได้ในผลลัพธ์ของคุณ: นวกรรมิกเรียกว่าสองครั้ง!

ในคลาสเฉพาะoperator newให้โทรหาผู้ให้บริการทั่วโลกโดยตรง:

return ::operator new(size);

ในทำนองเดียวกันในคลาสเฉพาะoperator deleteให้ทำ:

::operator delete(p);

อ้างoperator newถึงหน้าอ้างอิงสำหรับรายละเอียดเพิ่มเติม


ฉันรู้ว่านวกรรมิกเรียกว่าสองครั้งโดยการเรียก :: new in operator ใหม่และฉันหมายถึงมัน คำถามของฉันคือทำไม destructor ไม่ถูกเรียกเมื่อเรียก :: delete in operator delete?
expinc

1
@ expinc: จงใจเรียกตัวสร้างเป็นครั้งที่สองโดยไม่เรียกตัว destructor ก่อนเป็นความคิดที่แย่จริงๆ สำหรับ destructors ที่ไม่น่ารำคาญ (เช่นคุณ) คุณยังต้องเสี่ยงกับพฤติกรรมที่ไม่ได้กำหนด (ถ้าคุณต้องพึ่งพาผลข้างเคียงของ destructor ที่คุณทำ) - อ้างอิง [basic.life] §5 อย่าทำอย่างนี้
Sander De Dycker

1

ดูการอ้างอิง CPP :

operator delete, operator delete[]

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

ลบ (และใหม่) รับผิดชอบเฉพาะส่วน 'การจัดการหน่วยความจำ' เท่านั้น

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


1
ตัวดำเนินการลบควรยังคงเรียก destructor โดยนัยตามที่คุณเห็นจากบันทึกของเขาเองที่แสดงตัวทำลายหลังจากลบอินสแตนซ์ ปัญหาที่นี่คือการแทนที่การลบคลาสของเขากำลังเรียก :: ลบซึ่งนำไปสู่การแทนที่การลบทั่วโลกของเขา การลบแบบโกลบอลนั้นจะเป็นการเพิ่มหน่วยความจำเพียงอย่างเดียวดังนั้นมันจะไม่เรียกคืนตัวทำลาย
ดอง Rick

สถานะการอ้างอิงอย่างชัดเจนว่าการลบเรียกว่าการ
มาริโอเดอะช้อน

ใช่การลบทั่วโลกจะถูกเรียกหลังจากการลบคลาส มีการแทนที่สองที่นี่
ดอง Rick

2
@PickleRick - ในขณะที่มันเป็นความจริงที่การแสดงออกลบควรเรียก destructor (สมมติว่าให้ตัวชี้ไปยังประเภทที่มี destructor) หรือชุดของ destructor (รูปแบบอาร์เรย์) operator delete()ฟังก์ชั่นไม่เหมือนกับการลบนิพจน์ destructor ถูกเรียกก่อนที่จะเรียกใช้operator delete()ฟังก์ชัน
ปีเตอร์

1
มันจะช่วยถ้าคุณเพิ่มส่วนหัวใน qoute ขณะนี้ยังไม่ชัดเจนว่ามันหมายถึงอะไร "จัดสรรที่เก็บข้อมูล ... " - ยกเลิกการจัดสรรพื้นที่เก็บข้อมูลใคร
idclev 463035818
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.