2008-11-20 30 views
11

Vì không có finally trong C++ you have to use the RAII mẫu thiết kế thay thế, nếu bạn muốn mã của mình là ngoại lệ an toàn. Một cách để làm điều này là bằng cách sử dụng các destructor của một lớp địa phương như thế này:Thay thế thích hợp cho thiếu 'cuối cùng' trong C++

void foo() { 
    struct Finally { 
     ~Finally() { /* cleanup code */ } 
    } finalizer(); 
    // ...code that might throw an exception... 
} 

Đây là một lợi thế lớn so với các giải pháp thẳng về phía trước, bởi vì bạn không cần phải viết mã dọn dẹp 2 lần:

try { 
    // ...code that might throw an exception... 
    // cleanup code (no exception) 
} catch (...) { 
    // cleanup code (exception) 
    throw; 
} 

Một bất lợi lớn của giải pháp lớp địa phương là bạn không thể truy cập trực tiếp các biến cục bộ trong mã dọn dẹp của mình. Vì vậy, nó sẽ sưng lên mã của bạn rất nhiều nếu bạn cần truy cập vào chúng bất kể:

void foo() { 
    Task* task; 
    while (task = nextTask()) { 
     task->status = running; 
     struct Finally { 
      Task* task; 
      Finally(Task* task) : task(task) {} 
      ~Finally() { task->status = idle; } 
     } finalizer(task); 
     // ...code that might throw an exception... 
    } 
} 

Vì vậy, câu hỏi của tôi là: Có một giải pháp kết hợp cả hai lợi thế? Vì vậy, bạn a) không phải viết mã trùng lặp và b) có thể truy cập các biến cục bộ trong mã dọn dẹp, như task trong ví dụ cuối cùng, nhưng không có mã bloat như vậy.

+0

Đó là Ugly. Bạn nên tạo một RunObject hoặc một cái gì đó !!! –

+0

+1 để hỏi điều này bởi vì nó quá phổ biến để thấy những người không quen thuộc với 'RAII' nghĩ rằng' cuối cùng' là tốt ... –

+0

"bạn không phải viết mã dọn dẹp 2 lần". Tại sao bạn lại viết mã hai lần, vì lý do nào? Đó là những chương trình con cho = P Trung lập về câu hỏi của RAII vs cuối cùng, nhưng viết mã hai lần là một cá trích đỏ: bạn có thể tạo ra một thói quen dọn dẹp, truyền nhiệm vụ cho nó và gọi nó ở hai nơi. Không cần sao chép mã. Trong ví dụ đơn giản của bạn, một cách hợp lý là một phần của destructor của nhiệm vụ. Nhưng nếu có một số lý do bạn muốn dọn dẹp trong foo, sau đó tạo ra một thói quen dọn dẹp cục bộ ở đó. – ToolmakerSteve

Trả lời

15

Thay vì xác định struct Finally, bạn có thể trích xuất mã dọn dẹp của bạn trong một chức năng của lớp Task và sử dụng Loki ScopeGuard.

ScopeGuard guard = MakeGuard(&Task::cleanup, task); 

cũng Xem DrDobb's article này và other article này để biết thêm về ScopeGuards.

+0

Một chi tiết cần thận trọng là nếu mã dọn dẹp tự động ném, sử dụng ScopeGuard sẽ tự động loại bỏ trường hợp ngoại lệ đã ném (chắc chắn là hợp lý khi xử lý ngoại lệ trong C++) - ví dụ đầu tiên của op sẽ kết thúc và thứ hai sẽ ném ngoại lệ mới từ mã dọn dẹp thay vì ngoại lệ ban đầu, nếu bất kỳ –

8

Tôi không nghĩ rằng có một cách sạch hơn để đạt được những gì bạn đang cố gắng làm, nhưng tôi nghĩ rằng vấn đề chính với 'phương pháp cuối cùng' trong ví dụ của bạn là không đúng separation of concerns.

Ví dụ: Hàm foo() chịu trách nhiệm về tính nhất quán của đối tượng Task, điều này hiếm khi là một ý tưởng hay, các phương thức của Task sẽ chịu trách nhiệm thiết lập trạng thái cho một cái gì đó hợp lý.

Tôi nhận ra đôi khi có nhu cầu thực sự cuối cùng, và mã của bạn rõ ràng chỉ là một ví dụ đơn giản để hiển thị một điểm, nhưng những trường hợp này là hiếm. Và một chút mã contrived trong trường hợp hiếm hoi là chấp nhận được với tôi.

Những gì tôi đang cố gắng nói là, bạn hiếm khi có nhu cầu xây dựng cuối cùng, và đối với một vài trường hợp bạn làm, tôi muốn nói không lãng phí thời gian để xây dựng một số cách đẹp hơn. Nó sẽ chỉ khuyến khích bạn sử dụng cuối cùng nhiều hơn bạn thực sự nên ...

1

Như những người khác đã nói, "giải pháp" là tách mối quan tâm tốt hơn. Trong trường hợp của bạn, tại sao biến công việc không thể tự dọn dẹp sau chính nó? Nếu có bất kỳ dọn dẹp cần phải được thực hiện trên nó, sau đó nó không phải là một con trỏ, nhưng một đối tượng RAII.

void foo() { 
// Task* task; 
ScopedTask task; // Some type which internally stores a Task*, but also contains a destructor for RAII cleanup 
    while (task = nextTask()) { 
     task->status = running; 
     // ...code that might throw an exception... 
    } 
} 

con trỏ thông minh có thể là những gì bạn cần trong trường hợp này (tăng :: shared_ptr sẽ xóa con trỏ theo mặc định, nhưng bạn có thể chỉ định các chức năng tùy chỉnh deleter thay vào đó, có thể thực hiện tùy tiện dọn dẹp takss để thay thế. Đối với RAII trên con trỏ , đó là thường những gì bạn sẽ muốn.

vấn đề không phải là thiếu một cuối cùng từ khóa, nó mà bạn sử dụng con trỏ nguyên, mà không thể thực hiện RAII.

Nhưng thông thường, tất cả các loại nên biết làm thế nào để làm sạch sau khi chính nó.Không phải sau mỗi đối tượng nằm trong phạm vi khi ngoại lệ được ném ra (đó là điều cuối cùng đã làm và những gì bạn đang cố gắng làm), ngay sau chính nó. Và nếu mọi đối tượng làm điều đó, thì bạn không cần hàm catch-all lớn "dọn dẹp sau mỗi đối tượng trong phạm vi".

+0

xóa nhận xét của tôi, ví dụ socket của tôi là meh :) tôi nghĩ rằng tôi đồng ý có vài trường hợp cuối cùng có thể được sử dụng. nhưng nó sẽ được tốt đẹp anyway để có/mô phỏng nó :) –

-2

Tôi đã đến C + + từ Delphi để tôi biết những gì tôi đang nói về. Tôi ghét cuối cùng !!! Đó là xấu xí. Tôi thực sự không nghĩ rằng C + + là mất tích cuối cùng.

2

Tôi thường sử dụng một cái gì đó như thế này:

class Runner { 
private: 
    Task & task; 
    State oldstate; 
public: 
    Runner (Task &t, State newstate) : task(t), oldstate(t.status); 
    { 
    task.status = newstate; 
    }; 

    ~Runner() 
    { 
    task.status = oldstate; 
    }; 
}; 

void foo() 
{ 
    Task* task; 
    while (task = nextTask()) 
    { 
    Runner r(*task, running); 
      // ...code that might throw an exception... 
    } 
} 
Các vấn đề liên quan