2013-08-02 16 views
8

Đây không phải là một câu hỏi về lý do tại sao bạn sẽ viết mã như thế này, nhưng nhiều hơn như là một câu hỏi về cách một phương thức được thực hiện liên quan đến đối tượng mà nó được gắn với.Điều gì sẽ xảy ra nếu một đối tượng thay đổi kích cỡ vùng chứa của chính nó?

Nếu tôi có một cấu trúc như:

struct F 
{ 
    // some member variables 
    void doSomething(std::vector<F>& vec) 
    { 
     // do some stuff 
     vec.push_back(F()); 
     // do some more stuff 
    } 
} 

Và tôi sử dụng nó như thế này:

std::vector<F>(10) vec; 
vec[0].doSomething(vec); 

Điều gì xảy ra nếu push_back(...) trong doSomething(...) gây vector để mở rộng? Điều này có nghĩa là vec[0] sẽ được sao chép rồi xóa ở giữa khi thực hiện phương thức của nó. Điều này sẽ không tốt.

Ai đó có thể giải thích chính xác điều gì xảy ra ở đây?

  • Chương trình có bị lỗi ngay lập tức không? Liệu phương pháp chỉ cố gắng hoạt động trên dữ liệu không tồn tại?
  • Phương thức này có hoạt động "mồ côi" đối tượng của nó cho đến khi nó gặp sự cố như thay đổi trạng thái của đối tượng không?

Tôi quan tâm đến cách gọi phương thức có liên quan đến đối tượng được liên kết.

Trả lời

7

Vâng, rất tệ. Có thể đối tượng của bạn được sao chép (hoặc di chuyển trong C++ 11 nếu phân biệt có liên quan đến mã của bạn) trong khi bạn đang ở trong doSomething(). Vì vậy, sau khi hàm push_back() trả về, con trỏ này có thể không trỏ đến vị trí của đối tượng của bạn nữa. Đối với trường hợp cụ thể của vector :: push_back(), có thể bộ nhớ được trỏ đến bởi điều này đã được giải phóng và dữ liệu được sao chép vào một mảng mới ở một nơi khác. Đối với các thùng chứa khác (ví dụ, danh sách) để nguyên tố của chúng ở đúng vị trí, điều này (có thể) sẽ không gây ra vấn đề gì cả.

Trong thực tế, có khả năng mã của bạn sắp bị lỗi ngay lập tức. Trường hợp có khả năng nhất là ghi vào bộ nhớ trống và tham nhũng im lặng của trạng thái đối tượng F của bạn. Bạn có thể sử dụng các công cụ như valgrind để phát hiện loại hành vi này.

Nhưng về cơ bản bạn có ý tưởng đúng: đừng làm điều này, nó không an toàn.

+0

Cảm ơn câu trả lời. Bạn có nói rằng nó có khả năng chương trình có thể tiếp tục mà không bị rơi? Nếu tôi viết quá giới hạn của một mảng vào bộ nhớ trống, nó sẽ được phát hiện. Điều gì sẽ cho phép một phương pháp có thể viết vào bộ nhớ miễn phí mà không có vấn đề? – user487100

+2

Bạn hầu như luôn có thể ghi vào bộ nhớ "giải phóng" mà không gặp sự cố. Đó là cách hoạt động của ánh xạ bộ nhớ. Bản đồ vẫn được sử dụng cho các đối tượng trong tương lai. Bạn có thể ** không bao giờ ** viết vào bộ nhớ giải phóng "mà không có vấn đề", tuy nhiên. Nó chỉ là "vấn đề" là những thứ khác hơn là đâm. –

+0

@ user487100: Nếu bạn viết quá giới hạn của một mảng vào bộ nhớ trống, nó được phát hiện _sometimes_. Nó khá dễ dàng để tạo ra một trường hợp mà tôi có thể viết qua kết thúc và không có nó phát hiện trong một thời gian. Phân bổ một 'char * a = new char [1]', và sau đó đọc và ghi vào 'a [1]', nó có thể sẽ không kích hoạt bất kỳ lỗi nào trên hầu hết các hệ thống. –

3

Ai đó có thể giải thích chính xác điều gì xảy ra ở đây?

Có. Nếu bạn truy cập vào đối tượng, sau một push_back, resize hoặc insert đã phân bổ lại nội dung 's vector, đó là hành vi không xác định, có nghĩa là những gì thực xảy ra tùy thuộc vào trình biên dịch của bạn, hệ điều hành của bạn, những gì do some more stuff là gì và có thể một số các yếu tố khác như có thể là giai đoạn của mặt trăng, độ ẩm không khí ở một số địa điểm xa xôi, ... bạn đặt tên cho nó ;-)

Tóm lại, đây là (gián tiếp thông qua việc thực hiện std::vector) gọi hàm hủy của đối tượng, vì vậy thời gian tồn tại của đối tượng đã kết thúc. Hơn nữa, bộ nhớ bị chiếm trước đó bởi đối tượng đã được phát hành bởi bộ cấp phát của vector. Do đó, việc sử dụng các thành viên không tĩnh của đối tượng sẽ dẫn đến hành vi không xác định, vì con trỏ this được truyền cho hàm không trỏ đến một đối tượng nữa.Tuy nhiên, bạn có thể truy cập/gọi các thành viên tĩnh của lớp học:

struct F 
{ 
    static int i; 
    static int foo(); 

    double d; 
    void bar(); 

    // some member variables 
    void doSomething(std::vector<F>& vec) 
    { 
    vec.push_back(F()); 

    int n = foo(); //OK 
    i += n;  //OK 

    std::cout << d << '\n'; //UB - will most likely crash with access violation 
    bar();     //UB - what actually happens depends on the 
          //  implementation of bar 
    } 
}