2009-12-04 43 views
54

Có bao giờ kiểm tra xem liệu số này có bị vô hiệu không?Kiểm tra xem đây có phải là

Giả sử tôi có một lớp học với phương pháp; bên trong phương thức đó, tôi kiểm tra this == NULL và nếu có, hãy trả lại mã lỗi.

Nếu điều này là không, thì điều đó có nghĩa là đối tượng sẽ bị xóa. Liệu phương pháp này thậm chí có thể trả lại bất cứ điều gì?

Cập nhật: Tôi quên đề cập đến phương thức có thể được gọi từ nhiều luồng và có thể khiến đối tượng bị xóa trong khi một chuỗi khác nằm trong phương thức.

+4

Điều đó không có nghĩa là đối tượng đã bị xóa. Việc xóa một con trỏ sẽ không tự động xóa nó ra, và '((Foo *) 0) -> foo()' là cú pháp hoàn toàn hợp lệ. Chừng nào 'foo()' không phải là một hàm ảo, điều này thậm chí sẽ hoạt động trên hầu hết các trình biên dịch, nhưng nó chỉ là icky. –

+9

"có thể khiến đối tượng bị xóa trong khi một chuỗi khác nằm trong phương thức". Điều này là không thể chấp nhận được, bạn không được xóa một đối tượng trong khi mã khác (trong cùng một luồng hoặc một chuỗi khác) giữ lại một tham chiếu mà nó sẽ sử dụng. Nó cũng sẽ không khiến 'this' trở thành null trong chuỗi khác. –

+0

Tôi đang xem xét một tình huống ngay bây giờ, nơi đây chắc chắn là NULL. Đức Chúa Trời chỉ biết tại sao. – Owl

Trả lời

67

Làm cách nào để kiểm tra điều này == null? Tôi đã tìm thấy điều này trong khi thực hiện đánh giá mã.

Trong tiêu chuẩn C++, không có, vì bất kỳ cuộc gọi nào trên con trỏ null đều không được xác định, vì vậy bất kỳ mã nào dựa vào kiểm tra như vậy đều không chuẩn (không đảm bảo rằng séc sẽ được thực thi).

Lưu ý rằng điều này cũng đúng với các chức năng không phải ảo.

Một số triển khai cho phép this==0, tuy nhiên, và do đó các thư viện được viết cụ thể cho những triển khai đó đôi khi sẽ sử dụng nó làm hack. Một ví dụ tốt về một cặp như vậy là VC++ và MFC - Tôi không nhớ mã chính xác, nhưng tôi nhớ rõ ràng thấy việc kiểm tra if (this == NULL) trong mã nguồn MFC ở đâu đó.

Cũng có thể có hỗ trợ gỡ lỗi, vì tại một số điểm trong quá khứ mã này đã bị lỗi this==0 do lỗi trong trình gọi, do đó kiểm tra đã được chèn để bắt các phiên bản tương lai. Một khẳng định sẽ có ý nghĩa hơn cho những điều như vậy, mặc dù.

Nếu điều này == null thì điều đó có nghĩa là đối tượng sẽ bị xóa.

Không, điều đó không có nghĩa là. Nó có nghĩa là một phương thức được gọi trên một con trỏ null, hoặc trên một tham chiếu thu được từ một con trỏ null (mặc dù có được một tham chiếu như vậy đã là U.B.). Điều này không liên quan gì đến delete và không yêu cầu bất kỳ đối tượng nào thuộc loại này đã từng tồn tại.

+7

Với 'xóa', điều ngược lại thường đúng - nếu bạn' xóa' một đối tượng, nó không được đặt thành NULL, và sau đó bạn cố gắng gọi một phương thức trên nó, bạn sẽ thấy rằng 'this! = NULL', nhưng nó có thể sẽ bị lỗi hoặc hoạt động một cách kỳ quặc nếu bộ nhớ của nó đã được khai hoang để sử dụng bởi một số đối tượng khác. –

+1

IIRC, tiêu chuẩn cho phép 'xóa' để đặt con trỏ thành NULL, nhưng không yêu cầu nó, và trong thực tế hầu như không có triển khai nào làm như vậy. – rmeador

+4

'delete' có thể không phải lúc nào cũng nhận được giá trị l, xem xét' xóa này + 0; ' – GManNickG

24

Lưu ý của bạn về chủ đề là đáng lo ngại. Tôi khá chắc chắn rằng bạn có một điều kiện chủng tộc có thể dẫn đến một vụ tai nạn. Nếu một chủ đề xóa một đối tượng và số không con trỏ, một luồng khác có thể thực hiện cuộc gọi thông qua con trỏ đó giữa hai thao tác đó, dẫn đến this là không null và cũng không hợp lệ, dẫn đến sự cố. Tương tự, nếu một chuỗi gọi một phương thức trong khi một luồng khác đang ở giữa việc tạo đối tượng, bạn cũng có thể gặp sự cố.

Câu trả lời ngắn gọn, bạn thực sự cần phải sử dụng một mutex hoặc một cái gì đó để đồng bộ hóa quyền truy cập vào biến này. Bạn cần đảm bảo rằng thiskhông bao giờ không hoặc bạn sẽ gặp sự cố.

+5

"Bạn cần đảm bảo rằng điều này không bao giờ là rỗng" - tôi nghĩ cách tiếp cận tốt hơn là đảm bảo toán hạng bên trái của 'toán tử->' không bao giờ rỗng :) nhưng ngoài việc đó, tôi ước tôi có thể +10 điều này. –

6

FWIW, tôi đã sử dụng kiểm tra gỡ lỗi cho (this != NULL) trong các xác nhận trước đó đã giúp bắt mã lỗi. Không phải là mã sẽ nhất thiết phải đi quá xa với một vụ tai nạn, nhưng trên các hệ thống nhúng nhỏ không có bộ nhớ bảo vệ, các xác nhận thực sự đã giúp.

Trên các hệ thống có bảo vệ bộ nhớ, hệ điều hành thường sẽ vi phạm quyền truy cập nếu được gọi với con trỏ NULL this, vì vậy sẽ có ít giá trị hơn trong việc xác nhận this != NULL. Tuy nhiên, xem bình luận của Pavel về lý do tại sao nó không nhất thiết vô giá trị trên các hệ thống được bảo vệ.

+1

Vẫn còn một trường hợp để xác nhận, AV hay không. Vấn đề là một AV thường sẽ không xảy ra cho đến khi một hàm thành viên thực sự cố gắng truy cập một số trường thành viên. Khá thường xuyên họ chỉ gọi một cái gì đó khác (vv ...), cho đến khi cuối cùng một cái gì đó sụp đổ xuống dòng. Ngoài ra, họ có thể gọi một thành viên của một lớp khác hoặc một hàm toàn cầu, và chuyển (được cho là không null) 'this' như một đối số. –

+0

@Pavel: đủ chính xác - Tôi đã làm dịu đi những từ ngữ của mình về giá trị của việc khẳng định 'điều này' trên các hệ thống có bảo vệ bộ nhớ. –

+0

thực sự, nếu khẳng định là sẽ làm việc, sau đó chắc chắn mã chính cũng sẽ làm việc phải không? – Gokul

-1

Tôi cũng muốn thêm rằng thường tốt hơn là tránh null hoặc NULL. Tôi nghĩ rằng tiêu chuẩn đang thay đổi một lần nữa ở đây nhưng bây giờ 0 thực sự là những gì bạn muốn kiểm tra để được hoàn toàn chắc chắn rằng bạn đang nhận được những gì bạn muốn.

+0

Kiểm tra NULL không có điều như vậy. –

0

Phương pháp của bạn rất có thể (có thể khác nhau giữa các trình biên dịch) có thể chạy và cũng có thể trả về một giá trị. Miễn là nó không truy cập bất kỳ biến cá thể nào. Nếu nó cố gắng nó sẽ sụp đổ.

Vì những người khác đã chỉ ra rằng bạn không thể sử dụng kiểm tra này để xem một đối tượng có bị xóa hay không. Thậm chí nếu bạn có thể, nó sẽ không hoạt động, bởi vì đối tượng có thể bị xóa bởi một chuỗi khác ngay sau khi thử nghiệm nhưng trước khi bạn thực hiện dòng tiếp theo sau khi thử nghiệm. Sử dụng đồng bộ hóa Thread thay thế.

Nếu this là không có lỗi trong chương trình của bạn, rất có thể trong thiết kế chương trình của bạn.

-1

Đây chỉ là một con trỏ được chuyển làm đối số đầu tiên cho hàm (đó chính xác là điều làm cho nó trở thành một phương thức). Vì vậy, miễn là bạn không nói về phương pháp ảo và/hoặc thừa kế ảo, thì có, bạn có thể thấy mình thực thi một phương thức cá thể, với một cá thể rỗng. Như những người khác nói, bạn gần như chắc chắn sẽ không nhận được rất xa với thực hiện đó trước khi vấn đề phát sinh, nhưng mã hóa mạnh mẽ có lẽ nên kiểm tra tình hình đó, với một khẳng định. Ít nhất, nó có ý nghĩa khi bạn nghi ngờ nó có thể xảy ra vì lý do nào đó, nhưng cần phải theo dõi chính xác lớp nào/cuộc gọi ngăn xếp nó xảy ra.

-1

this == NULL có thể hữu ích để có một hành vi dự phòng (cho ví dụ một cơ chế ủy nhiệm cấp phát tùy chọn có thể dự phòng thành malloc/free). Tôi không chắc chắn tiêu chuẩn của nó nhưng nếu bạn KHÔNG BAO GIỜ gọi bất kỳ chức năng ảo nào trên đó và tất nhiên không có quyền truy cập thành viên bên trong phương thức trong nhánh đó, không có lý do thực sự nào xảy ra. Nếu không, bạn có trình biên dịch rất kém sử dụng con trỏ hàm ảo khi chúng không cần, và sau đó trước khi tuân thủ nghiêm ngặt tiêu chuẩn, bạn nên thay đổi trình biên dịch của mình.

Thỉnh thoảng nó rất hữu ích vì khả năng đọc và tái cấu trúc có thể trong rất ít trường hợp giành chiến thắng so với tiêu chuẩn (khi hành vi rõ ràng không được xác định cho tất cả các trình biên dịch hiện có).

Về bài viết: "Vẫn so sánh" này "Con trỏ tới Null" có thể lý do là tác giả của bài viết ít hiểu về trình biên dịch nào hơn nhóm phần mềm Microsoft đã viết MFC.

+0

"bạn có trình biên dịch rất kém sử dụng con trỏ hàm ảo khi chúng không cần, và trước khi tuân thủ nghiêm ngặt tiêu chuẩn, bạn nên thay đổi trình biên dịch của mình." - LOL, không. Điều này không liên quan gì đến ảo và mọi thứ liên quan đến thực hành mã hóa kém sử dụng UB. Sửa mã của bạn và sử dụng trình biên dịch cảnh báo, cho phép bạn tránh mã cho phép gỡ lỗi hoàn toàn không cần thiết hoặc các lỗ hổng bảo mật sau này. Ngoài ra: "khi hành vi rõ ràng của nó không được xác định cho tất cả các trình biên dịch hiện có" Tiêu chuẩn ra lệnh UB, không phải một số trình biên dịch. Dựa trên một thực hiện cụ thể của UB phải được tránh. –

4

Tôi biết rằng đây là cũ nhưng tôi cảm thấy như bây giờ mà chúng tôi đang đối phó với C++ 11-17 ai đó nên đề cập đến lambdas. Nếu bạn nắm bắt điều này vào một lambda mà sẽ được gọi là không đồng bộ tại một thời điểm sau đó trong thời gian, có thể là đối tượng "này" của bạn bị phá hủy trước khi lambda được gọi.

tức là đi qua nó như một callback để một số chức năng thời gian đắt tiền mà được điều hành từ một thread riêng biệt hay chỉ là không đồng bộ nói chung

EDIT: Chỉ cần được rõ ràng, câu hỏi là "Liệu nó có bao giờ có ý nghĩa để kiểm tra nếu điều này là null "Tôi chỉ cung cấp một kịch bản mà nó làm cho cảm giác rằng có thể trở nên phổ biến hơn với việc sử dụng rộng rãi hơn của C + + hiện đại.

Ví dụ bị truy tố: Mã này hoàn toàn có thể chạy được. Để xem hành vi không an toàn, chỉ cần nhận xét cuộc gọi đến hành vi an toàn và bỏ ghi chú cuộc gọi hành vi không an toàn.

#include <memory> 
#include <functional> 
#include <iostream> 
#include <future> 

class SomeAPI 
{ 
public: 
    SomeAPI() = default; 

    void DoWork(std::function<void(int)> cb) 
    { 
     DoAsync(cb); 
    } 

private: 
    void DoAsync(std::function<void(int)> cb) 
    { 
     std::cout << "SomeAPI about to do async work\n"; 
     m_future = std::async(std::launch::async, [](auto cb) 
     { 
      std::cout << "Async thread sleeping 10 seconds (Doing work).\n"; 
      std::this_thread::sleep_for(std::chrono::seconds{ 10 }); 
      // Do a bunch of work and set a status indicating success or failure. 
      // Assume 0 is success. 
      int status = 0; 
      std::cout << "Executing callback.\n"; 
      cb(status); 
      std::cout << "Callback Executed.\n"; 
     }, cb); 
    }; 
    std::future<void> m_future; 
}; 

class SomeOtherClass 
{ 
public: 
    void SetSuccess(int success) { m_success = success; } 
private: 
    bool m_success = false; 
}; 
class SomeClass : public std::enable_shared_from_this<SomeClass> 
{ 
public: 
    SomeClass(SomeAPI* api) 
     : m_api(api) 
    { 
    } 

    void DoWorkUnsafe() 
    { 
     std::cout << "DoWorkUnsafe about to pass callback to async executer.\n"; 
     // Call DoWork on the API. 
     // DoWork takes some time. 
     // When DoWork is finished, it calls the callback that we sent in. 
     m_api->DoWork([this](int status) 
     { 
      // Undefined behavior 
      m_value = 17; 
      // Crash 
      m_data->SetSuccess(true); 
      ReportSuccess(); 
     }); 
    } 

    void DoWorkSafe() 
    { 
     // Create a weak point from a shared pointer to this. 
     std::weak_ptr<SomeClass> this_ = shared_from_this(); 
     std::cout << "DoWorkSafe about to pass callback to async executer.\n"; 
     // Capture the weak pointer. 
     m_api->DoWork([this_](int status) 
     { 
      // Test the weak pointer. 
      if (auto sp = this_.lock()) 
      { 
       std::cout << "Async work finished.\n"; 
       // If its good, then we are still alive and safe to execute on this. 
       sp->m_value = 17; 
       sp->m_data->SetSuccess(true); 
       sp->ReportSuccess(); 
      } 
     }); 
    } 
private: 
    void ReportSuccess() 
    { 
     // Tell everyone who cares that a thing has succeeded. 
    }; 

    SomeAPI* m_api; 
    std::shared_ptr<SomeOtherClass> m_data = std::shared_ptr<SomeOtherClass>(); 
    int m_value; 
}; 

int main() 
{ 
    std::shared_ptr<SomeAPI> api = std::make_shared<SomeAPI>(); 
    std::shared_ptr<SomeClass> someClass = std::make_shared<SomeClass>(api.get()); 

    someClass->DoWorkSafe(); 

    // Comment out the above line and uncomment the below line 
    // to see the unsafe behavior. 
    //someClass->DoWorkUnsafe(); 

    std::cout << "Deleting someClass\n"; 
    someClass.reset(); 

    std::cout << "Main thread sleeping for 20 seconds.\n"; 
    std::this_thread::sleep_for(std::chrono::seconds{ 20 }); 

    return 0; 
} 
+2

Nhưng ngay cả khi đối tượng bị phá hủy, sẽ không phải lambda kết thúc bằng một con trỏ 'this' bị treo lơ lửng không null? – interfect

+3

Chính xác! Kiểm tra nếu "this" == nullptr hoặc NULL sẽ không đủ trong trường hợp này là "this" sẽ bị treo lơ lửng. Tôi chỉ đơn thuần đề cập đến nó vì một số người hoài nghi về một ngữ nghĩa như vậy thậm chí cần phải tồn tại. –

+0

Tôi không nghĩ rằng câu trả lời này là có liên quan như câu hỏi chỉ quan tâm "Nói rằng tôi có một lớp học với một phương pháp, bên trong phương pháp đó". Lambdas chỉ là một ví dụ về nơi bạn có thể nhận được một con trỏ lơ lửng. – user2672165

0

Tôi biết đây là một câu hỏi cũ, tuy nhiên tôi nghĩ rằng tôi sẽ chia sẻ kinh nghiệm của tôi với việc sử dụng Lambda chụp

#include <iostream> 
#include <memory> 

using std::unique_ptr; 
using std::make_unique; 
using std::cout; 
using std::endl; 

class foo { 
public: 
    foo(int no) : no_(no) { 

    } 

    template <typename Lambda> 
    void lambda_func(Lambda&& l) { 
     cout << "No is " << no_ << endl; 
     l(); 
    } 

private: 
    int no_; 
}; 

int main() { 
    auto f = std::make_unique<foo>(10); 

    f->lambda_func([f = std::move(f)]() mutable { 
     cout << "lambda ==> " << endl; 
     cout << "lambda <== " << endl; 
    }); 

    return 0; 
} 

Mã này lỗi phân khúc

$ g++ -std=c++14 uniqueptr.cpp 
$ ./a.out 
Segmentation fault (core dumped) 

Nếu tôi loại bỏ các std::cout tuyên bố từ lambda_func Mã chạy để hoàn thành.

Dường như, tuyên bố này f->lambda_func([f = std::move(f)]() mutable { xử lý các lần chụp lambda trước khi hàm thành viên được gọi.

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