2013-01-19 14 views
5

Tôi nhìn vào đoạn mã sau đây danh sách liên kết Tôi tìm thấy trực tuyến:C++ - Tại sao đặt đối tượng thành không sau khi xóa?

void DeleteAfter(Node **head){ 
     if(*head==NULL){ 
      return; 
     }else{ 
      Node *temp = NULL; 
      temp = (*head)->next; 
      (*head)->next = (*head)->next->next; 
      delete temp; 
      temp=NULL; 
     } 
} 

Tôi không phải là tay nghề cao với C++, vì vậy đây có thể là một câu hỏi xấu, nhưng tại sao là tạm thời được thiết lập để NULL sau khi bị đã xóa? Đây có phải là một bước cần thiết không?

+1

Không. Chỉ cần sử dụng một con trỏ thông minh. – chris

+3

ở đây nó là hoàn toàn không thích hợp cho dù bạn đặt nó vào NULL hay không. 'temp' là một biến với lưu trữ tự động, có nghĩa là nó sẽ nằm ngoài phạm vi sau khi thoát khỏi khối' else'. nhưng như @chris nói, chỉ cần sử dụng con trỏ thông minh –

+2

cũng, thực tế là '* head' không phải là' NULL' không có nghĩa là '(* head) -> next' không phải là' NULL', và bạn đang cố gắng dereference con trỏ đó ('(* đầu) -> tiếp theo -> ...') –

Trả lời

7

Không cần thiết. Một số người có thói quen làm điều này ngay cả khi nó không có kết quả. Trình tối ưu hóa trình biên dịch tích cực sẽ loại bỏ mã này, vì vậy nó không thực sự gây hại. Nhưng tôi đã viết:

void DeleteAfter(Node *head) { 
    if (head) { 
    Node *next = head->next; 
    if (next) { 
     head->next = next->next; 
     delete next; 
    } 
    } 
} 

Lưu ý Tôi đã loại bỏ mức độ vô hướng và thêm séc để đảm bảo có "nút sau" để xóa.

Lý do cơ bản cho thói quen là nếu con trỏ luôn là là đối tượng hợp lệ hoặc không có giá trị, bạn có thể dựa vào kiểm tra trống tương đương với kiểm tra tính hợp lệ. Vì lý do này, Ada, một ngôn ngữ thường được sử dụng trong các hệ thống an toàn quan trọng, khởi tạo con trỏ thành null và xác định toán tử tương đương delete của nó để tự động đặt đối số của nó một cách tự động. C++ của bạn đang mô phỏng hành vi này.

Trong thực tế, giá trị của kỷ luật này không phải là điều bạn mong muốn. Một lần trong một thời gian dài nó ngăn chặn một lỗi ngớ ngẩn. Một điều tốt đẹp, tuy nhiên, là trình gỡ lỗi hiển thị nội dung con trỏ có ý nghĩa.

+0

Hãy nhớ rằng đầu-> tiếp theo có thể là NULL khiến mã này bị lỗi ở đầu-> next = head-> next-> next; – drescherjm

+0

Vì vậy, sự thật ... Tôi đã cập nhật tài khoản cho điều này. Nó không có ý nghĩa để kiểm tra các nút đầu và không đầu-> tiếp theo. – Gene

0

Không cần thiết và một số (bao gồm cả tôi) coi đó là hành vi không tốt.

Động lực để đặt thành NULL là bạn có thể kiểm tra sau đó xem liệu nó có bị xóa hay không và truy cập nếu không. Ngoài ra, điều này sẽ ngăn chặn một xóa kép, bởi vì delete trên một con trỏ NULL là một no-op.

Mặt khác, nó có thể ẩn các lỗi. Nếu đối tượng đã bị xóa, không có điểm trong việc sử dụng nó, phải không? Bạn nên biết rằng đối tượng đã bị xóa, không dựa vào séc.

Ví dụ

if (p != NULL) //or just if (p) 
    p->doStuff() 

Tại sao? Bạn chưa biết liệu nó có bị xóa hay không? Không phải là một phần của logic?

1

Bạn không phải đặt biến con trỏ cục bộ thành NULL sau khi xóa. Bạn nên đặt con trỏ thành NULL nếu bạn muốn sử dụng lại con trỏ, sau khi kiểm tra NULL, u có thể gán địa chỉ mới cho nó một cách an toàn. nó cho các biến thành viên con trỏ và các biến con trỏ toàn cục.

1

Nếu temp là biến toàn cầu hoặc biến thành viên, thì việc đặt thành NULL không phải là một ý tưởng tồi.

Tôi đã có thói quen thiết lập con trỏ tới NULL sau khi sử dụng bộ thu gom bảo thủ với mã C. Không có con trỏ đến bộ nhớ không sử dụng là làm thế nào nó tìm thấy rác để thu thập. Nhưng trong trường hợp này, bạn cũng nên làm

temp->next = NULL; 
1

Nếu biến số temp có thể được sử dụng lại sau này, thì tốt nhất là đặt giá trị NULL.

Có hai lý do bạn thường đặt con trỏ thành NULL sau khi phát hành nó.

1.) Khi bạn nhả con trỏ, bộ nhớ tại địa chỉ được trỏ đến không còn có sẵn cho chương trình của bạn nữa. Về mặt lý thuyết, bộ nhớ đó hiện có thể được sử dụng bởi bất kỳ chương trình nào khác, kể cả hệ điều hành! Cố gắng để phát hành một con trỏ đã được phát hành và do đó chỉ ra những người biết những gì có thể gây ra một vấn đề lớn. Các hệ điều hành hiện đại may mắn bảo vệ chống lại điều này nhưng chương trình vẫn sẽ bị lỗi với một lỗi truy cập bất hợp pháp. Phát hành một con trỏ null OTOH sẽ làm hoàn toàn không có gì.

2.) Bạn nên luôn luôn kiểm tra xem con trỏ không phải là NULL trước khi tham chiếu nó với toán tử *. De-tham chiếu một con trỏ NULL sẽ gây ra một lỗi thời gian chạy. De-tham chiếu một con trỏ được phát hành trỏ đến một số bộ nhớ tùy ý thậm chí còn tồi tệ hơn. Một con trỏ được giải phóng phải luôn được đặt thành NULL để mã sau có thể giả định một điểm con trỏ không null tới dữ liệu hợp lệ. Nếu không thì không có cách nào để biết liệu con trỏ có còn hợp lệ hay không.

Đối với câu hỏi ban đầu, biến con trỏ temp được khai báo dưới dạng biến cục bộ trong một hàm ngắn mà ở đó không bao giờ được sử dụng lại. Trong trường hợp này, nó không cần thiết để đặt nó thành NULL vì nó nằm ngoài phạm vi ngay sau khi hàm trả về.

Tuy nhiên, dòng ...

(*head)->next = (*head)->next->next; 

thất bại trong việc đảm bảo (*head)->next không phải là null trước khi cố gắng bỏ tham khảo, một không-không.

Một phiên bản tốt hơn sẽ là ...

int DeleteAfter(Node **head){ 
    Node *node_after = NULL; 

    if(*head==NULL) 
    return -1; 

    node_after = (*head)->next; 

    if(node_after == NULL) 
    return -1; 

    (*head)->next = node_after->next; 
    delete node_after; 

    return 0; 
    } 

Lúc này người sử dụng chức năng có thể kiểm tra xem việc xóa nút đã thành công bởi giá trị trả về và không có nguy cơ cố gắng để xóa một không tồn tại nút.

0

Trong ví dụ mã của bạn, không có lợi ích ngay lập tức rõ ràng, tuy nhiên có thể cho là lợi ích chi phí bảo trì cho người độc lập. Ý tưởng là một người nào đó cuối cùng có thể thêm mã sau khi xóa temp của bạn mà cố gắng để dereference temp. Điều này có thể xảy ra chỉ đơn giản bằng cách không nhận thấy việc xóa, hoặc bằng cách di chuyển các dòng trước đó truy cập temp sau khi xóa.

Dưới đây là một ví dụ:

int * i = new int(12); 
std::cout << *i << std::endl; // output is 12. 
delete i; 
// i = 0; // This would cause the program to fail on the next line. 
std::cout << *i << std::endl; // output is random for me. 

Lưu ý rằng điều này không che giấu một khiếm khuyết, trên thực tế không phải thiết lập các con trỏ null sẽ, trong trường hợp này, giấu những khiếm khuyết như * i trả về một giá trị ngẫu nhiên .

Hầu hết sẽ nói i = 0 có khả năng được tối ưu hóa bởi trình biên dịch, hoặc cách gán cho con trỏ hầu hết là vô hại. Đối với tôi, tôi luôn luôn thận trọng khi phát triển một cách chuyên nghiệp.

Các vấn đề liên quan