Là xóa điều này được cho phép?


232

Có được phép delete this;nếu câu lệnh xóa là câu lệnh cuối cùng sẽ được thực thi trong trường hợp đó của lớp không? Tất nhiên tôi chắc chắn rằng đối tượng được đại diện bởi this-pulum là newly-created.

Tôi đang nghĩ về một cái gì đó như thế này:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Tôi có thể làm điều này?


13
Vấn đề chính là nếu bạn delete thisđã tạo ra một khớp nối chặt chẽ giữa lớp và phương thức phân bổ được sử dụng để tạo các đối tượng của lớp đó. Đó là thiết kế OO rất kém, vì điều cơ bản nhất trong OOP là tạo ra các lớp tự trị mà không biết hoặc không quan tâm đến những gì người gọi của họ đang làm. Do đó, một lớp được thiết kế đúng không nên biết hoặc quan tâm đến cách phân bổ. Nếu bạn vì một lý do nào đó cần một cơ chế đặc biệt như vậy, tôi nghĩ rằng một thiết kế tốt hơn sẽ là sử dụng một lớp bao bọc xung quanh lớp thực tế và để cho trình bao bọc xử lý việc phân bổ.
Lundin

Bạn không thể xóa trong setWorkingModule?
Jimmy T.

Câu trả lời:


238

C ++ FAQ Lite có một mục cụ thể cho việc này

Tôi nghĩ rằng trích dẫn này tổng hợp độc đáo

Miễn là bạn cẩn thận, việc một đối tượng tự tử (xóa cái này) cũng không sao.


15
FQA tương ứng cũng có một số nhận xét hữu ích: yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C.

1
Để an toàn, bạn có thể sử dụng hàm hủy riêng trên đối tượng ban đầu để đảm bảo nó không được xây dựng trên ngăn xếp hoặc là một phần của mảng hoặc vectơ.
Cem Kalyoncu

Xác định 'cẩn thận'
CinCout

3
"Cẩn thận" được định nghĩa trong bài viết Câu hỏi thường gặp được liên kết. (Trong khi liên kết FQA chủ yếu là rant - giống như hầu hết mọi thứ trong đó - C ++ tệ đến mức nào)
CharonX

88

Có, delete this;đã xác định kết quả, miễn là (như bạn đã lưu ý), bạn đảm bảo đối tượng được phân bổ động và (tất nhiên) không bao giờ cố gắng sử dụng đối tượng sau khi bị phá hủy. Trong những năm qua, nhiều câu hỏi đã được hỏi về những gì tiêu chuẩn nói cụ thể delete this;, trái ngược với việc xóa một số con trỏ khác. Câu trả lời khá ngắn gọn và đơn giản: nó không nói lên điều gì nhiều. Nó chỉ nói rằng deletetoán hạng phải là một biểu thức chỉ định một con trỏ tới một đối tượng hoặc một mảng các đối tượng. Nó đi sâu vào khá nhiều chi tiết về những thứ như cách nó tìm ra chức năng phân bổ (nếu có) để gọi giải phóng bộ nhớ, nhưng toàn bộ phần trên delete(§ [expr.delete]) hoàn toàn không đề cập đến delete this;. Phần về phá hủy không đề cập đếndelete this ở một nơi (§ [class.dtor] / 13):

Tại điểm định nghĩa của hàm hủy ảo (bao gồm cả định nghĩa ngầm (15.8)), hàm giải quyết không mảng được xác định như thể đối với biểu thức xóa, điều này xuất hiện trong hàm hủy không ảo của lớp của hàm hủy (xem 8.3.5 ).

Điều đó có xu hướng ủng hộ ý tưởng rằng tiêu chuẩn coi delete this;là hợp lệ - nếu nó không hợp lệ, loại của nó sẽ không có ý nghĩa. Đó là nơi duy nhất đề cập đến tiêu chuẩn delete this;, theo như tôi biết.

Dù sao, một số người coi là delete thismột hack khó chịu, và nói với bất cứ ai sẽ lắng nghe rằng nó nên được tránh. Một vấn đề thường được trích dẫn là khó khăn trong việc đảm bảo rằng các đối tượng của lớp chỉ được phân bổ động. Những người khác coi đó là một thành ngữ hoàn toàn hợp lý, và sử dụng nó mọi lúc. Cá nhân tôi đang ở đâu đó ở giữa: Tôi hiếm khi sử dụng nó, nhưng đừng ngần ngại làm điều đó khi nó dường như là công cụ phù hợp cho công việc.

Lần đầu tiên bạn sử dụng kỹ thuật này là với một đối tượng có cuộc sống gần như hoàn toàn. Một ví dụ James Kanze đã trích dẫn là một hệ thống thanh toán / theo dõi mà anh ta làm việc cho một công ty điện thoại. Khi bạn bắt đầu thực hiện một cuộc gọi điện thoại, một cái gì đó lưu ý về điều đó và tạo ra một phone_callđối tượng. Từ thời điểm đó trở đi, phone_callđối tượng xử lý các chi tiết của cuộc gọi điện thoại (tạo kết nối khi bạn quay số, thêm một mục vào cơ sở dữ liệu để nói khi cuộc gọi bắt đầu, có thể kết nối nhiều người hơn nếu bạn thực hiện cuộc gọi hội nghị, v.v.) những người cuối cùng trong cuộc gọi cúp máy, phone_callđối tượng thực hiện việc giữ sổ cuối cùng của nó (ví dụ: thêm một mục vào cơ sở dữ liệu để nói khi bạn gác máy, để họ có thể tính toán thời gian cuộc gọi của bạn là bao lâu) và sau đó tự hủy. Cuộc đời củaphone_callĐối tượng dựa trên thời điểm người đầu tiên bắt đầu cuộc gọi và khi người cuối cùng rời khỏi cuộc gọi - từ quan điểm của phần còn lại của hệ thống, về cơ bản nó hoàn toàn tùy ý, vì vậy bạn không thể buộc nó vào bất kỳ phạm vi từ vựng nào trong mã hoặc bất cứ thứ gì theo thứ tự đó.

Đối với bất kỳ ai có thể quan tâm đến mức độ đáng tin cậy của loại mã hóa này: nếu bạn gọi điện thoại đến, từ hoặc qua hầu hết mọi nơi ở Châu Âu, có một cơ hội khá tốt rằng nó được xử lý (ít nhất là một phần) bằng mã điều đó thực hiện chính xác điều này.


2
Cảm ơn, tôi sẽ đặt nó ở đâu đó trong trí nhớ của tôi. Tôi giả sử bạn định nghĩa các hàm tạo và hàm hủy là riêng tư và sử dụng một số phương thức tĩnh của nhà máy để tạo các đối tượng đó.
Alexandre C.

@Alexandre: Dù sao thì bạn cũng có thể làm điều đó trong hầu hết các trường hợp - Tôi không biết bất cứ nơi nào gần với tất cả các chi tiết của hệ thống mà anh ta đang làm việc, vì vậy tôi không thể nói chắc chắn về điều đó.
Jerry Coffin

Cách tôi thường gặp phải xung quanh vấn đề làm thế nào bộ nhớ được phân bổ là bao gồm một bool selfDeletetham số trong hàm tạo được gán cho một biến thành viên. Cấp, điều này có nghĩa là bàn giao cho lập trình viên đủ dây để buộc một thòng lọng trong đó, nhưng tôi thấy điều đó thích hợp hơn với rò rỉ bộ nhớ.
MBraedley

1
@MBraedley: Tôi cũng đã làm như vậy, nhưng muốn tránh những gì dường như với tôi như một loại bùn.
Jerry Coffin

Đối với bất kỳ ai có thể quan tâm ... có một cơ hội khá tốt là nó được xử lý (ít nhất là một phần) bằng mã chính xác this. Có, mã đang được xử lý chính xác this. ;)
Galaxy

46

Nếu điều đó làm bạn sợ, có một vụ hack hoàn toàn hợp pháp:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Tôi nghĩ delete thislà C ++ thành ngữ, và tôi chỉ trình bày điều này như một sự tò mò.

Có một trường hợp cấu trúc này thực sự hữu ích - bạn có thể xóa đối tượng sau khi ném một ngoại lệ cần dữ liệu thành viên khỏi đối tượng. Đối tượng vẫn còn hiệu lực cho đến sau khi ném diễn ra.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Lưu ý: nếu bạn đang sử dụng trình biên dịch cũ hơn C ++ 11, bạn có thể sử dụng std::auto_ptrthay thế std::unique_ptr, nó sẽ làm điều tương tự.


Tôi không thể biên dịch nó bằng c ++ 11, có một số tùy chọn trình biên dịch đặc biệt cho nó không? Nó cũng không yêu cầu di chuyển con trỏ này?

@Owl không chắc ý của bạn là gì, nó hoạt động với tôi: ideone.com/aavQUK . Tạo một unique_ptrtừ khác unique_ptr đòi hỏi phải có một động thái, nhưng không phải từ một con trỏ thô. Trừ khi mọi thứ thay đổi trong C ++ 17?
Đánh dấu tiền chuộc

Ahh C ++ 14, đó sẽ là lý do. Tôi cần cập nhật c ++ của mình trên hộp dev. Tôi sẽ thử lại tối nay trên hệ thống gentoo mới nổi của tôi!

25

Một trong những lý do mà C ++ được thiết kế là để giúp dễ dàng sử dụng lại mã. Nói chung, C ++ nên được viết để nó hoạt động cho dù lớp được khởi tạo trên heap, trong một mảng hoặc trên ngăn xếp. "Xóa cái này" là một thực hành mã hóa rất tệ bởi vì nó sẽ chỉ hoạt động nếu một trường hợp duy nhất được xác định trên heap; và tốt hơn hết là không có một câu lệnh xóa khác, thường được sử dụng bởi hầu hết các nhà phát triển để dọn sạch đống. Làm điều này cũng giả định rằng không có lập trình viên bảo trì trong tương lai sẽ chữa được rò rỉ bộ nhớ nhận thức sai bằng cách thêm một câu lệnh xóa.

Ngay cả khi bạn biết trước rằng kế hoạch hiện tại của bạn là chỉ phân bổ một thể hiện duy nhất trên heap, điều gì sẽ xảy ra nếu một nhà phát triển hạnh phúc nào đó xuất hiện trong tương lai và quyết định tạo một cá thể trên stack? Hoặc, điều gì sẽ xảy ra nếu anh ta cắt và dán một số phần nhất định của lớp sang một lớp mới mà anh ta dự định sử dụng trên ngăn xếp? Khi mã đạt đến "xóa cái này", nó sẽ tắt và xóa nó, nhưng sau đó khi đối tượng đi ra khỏi phạm vi, nó sẽ gọi hàm hủy. Kẻ hủy diệt sau đó sẽ cố gắng xóa nó một lần nữa và sau đó bạn bị hos. Trong quá khứ, làm một cái gì đó như thế này sẽ làm hỏng không chỉ chương trình mà cả hệ điều hành và máy tính sẽ cần phải được khởi động lại. Trong mọi trường hợp, điều này rất KHÔNG được khuyến khích và hầu như luôn luôn nên tránh. Tôi sẽ phải tuyệt vọng, nghiêm túc trát,


7
+1. Tôi không thể hiểu tại sao bạn bị hạ cấp. "C ++ nên được viết để nó hoạt động cho dù lớp được khởi tạo trên heap, trong một mảng hoặc trên ngăn xếp" là lời khuyên rất tốt.
Joh

1
Bạn chỉ có thể bọc đối tượng bạn muốn xóa chính nó trong một lớp đặc biệt để xóa đối tượng và sau đó chính nó và sử dụng kỹ thuật này để ngăn chặn phân bổ ngăn xếp: stackoverflow.com/questions/124880/. Có những lúc thực sự không có thay thế khả thi. Tôi chỉ sử dụng kỹ thuật này để tự xóa một luồng được bắt đầu bởi hàm DLL, nhưng hàm DLL phải trả về trước khi luồng kết thúc.
Felix Dombek

Bạn không thể lập trình theo cách mà một người nào đó chỉ cần sao chép và dán mã của bạn sẽ bị lạm dụng nó
Jimmy T.

22

Nó được cho phép (chỉ không sử dụng đối tượng sau đó), nhưng tôi sẽ không viết mã như vậy trên thực tế. Tôi nghĩ rằng delete thischỉ nên xuất hiện trong các chức năng được gọi releasehoặc Releasevà trông giống như : void release() { ref--; if (ref<1) delete this; }.


Đó chính xác là một lần trong mỗi dự án của tôi ... :-)
cmaster - phục hồi monica

15

Chà, trong cấu trúc Mô hình đối tượng thành phần (COM) delete thiscó thể là một phần của Releasephương thức được gọi bất cứ khi nào bạn muốn giải phóng đối tượng ngậm nước:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}

8

Đây là thành ngữ cốt lõi cho các đối tượng được tính tham chiếu.

Đếm tham chiếu là một hình thức mạnh mẽ của bộ sưu tập rác xác định - nó đảm bảo các đối tượng quản lý vòng đời OWN của họ thay vì dựa vào các con trỏ 'thông minh', v.v. để làm điều đó cho họ. Đối tượng cơ bản chỉ được truy cập thông qua các con trỏ thông minh "Tham chiếu", được thiết kế sao cho các con trỏ tăng và giảm một số nguyên thành viên (số tham chiếu) trong đối tượng thực tế.

Khi tham chiếu cuối cùng rơi ra khỏi ngăn xếp hoặc bị xóa, số tham chiếu sẽ về không. Hành vi mặc định của đối tượng của bạn sau đó sẽ là một lệnh gọi "xóa cái này" để thu gom rác - các thư viện tôi viết cung cấp một cuộc gọi "CountIsZero" ảo được bảo vệ trong lớp cơ sở để bạn có thể ghi đè hành vi này cho những thứ như bộ nhớ đệm.

Chìa khóa để làm cho điều này an toàn là không cho phép người dùng truy cập vào XÂY DỰNG của đối tượng được đề cập (làm cho nó được bảo vệ), mà thay vào đó, làm cho họ gọi một số thành viên tĩnh - như FACTORY - như "Tạo tham chiếu tĩnh (...)". Bằng cách đó, bạn BIẾT chắc chắn rằng chúng luôn được xây dựng với "mới" thông thường và không có con trỏ thô nào có sẵn, vì vậy "xóa cái này" sẽ không bao giờ nổ tung.


Tại sao bạn không thể có một "bộ phân phối / bộ thu gom rác" lớp (singleton), một giao diện thông qua đó tất cả việc phân bổ được thực hiện và để lớp đó xử lý tất cả việc đếm tham chiếu của các đối tượng được phân bổ? Thay vì buộc các đối tượng phải bận tâm với các nhiệm vụ thu gom rác, một cái gì đó hoàn toàn không liên quan đến mục đích được chỉ định của họ.
Lundin

1
Bạn cũng có thể làm cho hàm hủy được bảo vệ để cấm phân bổ tĩnh và ngăn xếp đối tượng của bạn.
Game_Overture

7

Bạn có thể làm như vậy. Tuy nhiên, bạn không thể chỉ định điều này. Do đó, lý do bạn nêu ra để làm điều này, "Tôi muốn thay đổi quan điểm", có vẻ rất đáng nghi ngờ. Theo tôi, phương pháp tốt hơn sẽ dành cho đối tượng giữ khung nhìn để thay thế khung nhìn đó.

Tất nhiên, bạn đang sử dụng các đối tượng RAII và vì vậy bạn thực sự không cần phải gọi xóa ... phải không?


4

Đây là một câu hỏi cũ, đã được trả lời, nhưng @Alexandre đã hỏi "Tại sao mọi người muốn làm điều này?", Và tôi nghĩ rằng tôi có thể cung cấp một ví dụ sử dụng mà tôi đang xem xét chiều nay.

Mã di sản. Sử dụng con trỏ trần Obj * obj với một obj xóa ở cuối.

Thật không may, đôi khi tôi cần, không thường xuyên, để giữ cho đối tượng sống lâu hơn.

Tôi đang xem xét làm cho nó một tham chiếu đếm con trỏ thông minh. Nhưng sẽ có rất nhiều mã để thay đổi, nếu tôi sử dụngref_cnt_ptr<Obj> ở mọi nơi. Và nếu bạn trộn Obj * và ref_cnt_ptr trần trụi, bạn có thể khiến đối tượng bị xóa hoàn toàn khi ref_cnt_ptr cuối cùng biến mất, mặc dù vẫn còn Obj *.

Vì vậy, tôi đang suy nghĩ về việc tạo một tệp_d_deteetefff_cnt_ptr. Tức là một con trỏ đếm tham chiếu trong đó việc xóa chỉ được thực hiện trong một thói quen xóa rõ ràng. Sử dụng nó ở một nơi mà mã hiện có biết thời gian tồn tại của đối tượng, cũng như trong mã mới của tôi giúp đối tượng tồn tại lâu hơn.

Tăng và giảm số tham chiếu là tường minh_bảng_fete_fr_ptr bị thao túng.

Nhưng KHÔNG giải phóng khi số tham chiếu được xem là bằng 0 trong hàm hủy tường minh_delete_Vf_cnt_ptr.

Chỉ giải phóng khi số tham chiếu được xem là 0 trong một hoạt động giống như xóa rõ ràng. Ví dụ như trong một cái gì đó như:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

OK, một cái gì đó như thế. Có một chút khác thường khi có một loại con trỏ đếm tham chiếu không tự động xóa đối tượng được trỏ đến trong hàm hủy ptr của RC. Nhưng có vẻ như điều này có thể làm cho việc trộn con trỏ trần và con trỏ RC an toàn hơn một chút.

Nhưng cho đến nay không cần phải xóa điều này.

Nhưng sau đó nó đã xảy ra với tôi: nếu đối tượng được trỏ đến, pointee, biết rằng nó đang được tham chiếu, ví dụ nếu số đếm nằm trong đối tượng (hoặc trong một số bảng khác), thì thường trình xóa_if_rc0 có thể là một phương thức của đối tượng pointee, không phải con trỏ (thông minh).

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

Trên thực tế, nó không cần phải là một phương thức thành viên, nhưng có thể là một chức năng miễn phí:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(BTW, tôi biết mã không hoàn toàn đúng - nó trở nên ít đọc hơn nếu tôi thêm tất cả các chi tiết, vì vậy tôi sẽ để nó như thế này.)


0

Xóa điều này là hợp pháp miễn là đối tượng trong đống. Bạn sẽ cần yêu cầu đối tượng chỉ là đống. Cách duy nhất để làm điều đó là làm cho hàm hủy được bảo vệ - cách xóa này có thể được gọi là CHỈ từ lớp, vì vậy bạn sẽ cần một phương thức đảm bảo xóa

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.