2013-09-25 69 views
7

Hãy tưởng tượng rằng tôi có việc phải làm, có thể được thực hiện theo ba cách khác nhau: một cách chậm và đau đớn nhưng không an toàn; con đường với đau khổ vừa phải, cho bạn có Resource1; và một cách nhanh chóng và dễ dàng, yêu cầu cả hai số Resource1Resource2. Bây giờ, những nguồn lực quý giá để tôi bọc chúng vào RAII-thực hiện ResNHolder s và viết một cái gì đó như thế này:RAII và ngoại lệ trong nhà xây dựng

void DoTheJob(Logger& log/*, some other params */) { 
    try { 
     Res1Holder r1(/* arguments for creating resource #1 */); 
     try { 
      Res2Holder r2(/* arguments */); 
      DoTheJobQuicklyAndEasily(log, r1, r2); 
     } 
     catch (Res2InitializationException& e) { 
      log.log("Can't obtain resource 2, that'll slowdown us a bit"); 
      DoTheJobWithModerateSuffering(log, r1); 
     } 
    } 
    catch (Res1InitializationException& e) { 
     log.log("Can't obtain resource 1, using fallback"); 
     DoTheJobTheSlowAndPainfulWay(log); 
    } 
} 

"DoTheJobXxx()" lấy tham chiếu đến Logger/ResNHolder, bởi vì họ đều là phòng không copyable. Tôi có làm nó quá vụng về không? Có cách nào khác thông minh để cấu trúc các chức năng?

+2

tôi nghĩ rằng đó là tốt. – Nawaz

+1

Điều này có thể là một ví dụ về sách giáo khoa để thử nắm bắt. –

+1

Tôi sẽ sử dụng phương thức factory trả về đối tượng tùy chọn thay vì ngoại lệ. – yngccc

Trả lời

2

Tôi nghĩ rằng mã của bạn sẽ là tốt, nhưng đây là một sự thay thế để xem xét:

void DoTheJob(Logger &log/*,args*/) 
{ 
    std::unique_ptr<Res1Holder> r1 = acquireRes1(/*args*/); 
    if (!r1) { 
     log.log("Can't acquire resource 1, using fallback"); 
     DoTheJobTheSlowAndPainfulWay(log); 
     return; 
    } 
    std::unique_ptr<Res2Holder> r2 = acquireRes2(/*args*/); 
    if (!r2) { 
     log.log("Can't acquire resource 2, that'll slow us down a bit."); 
     DoTheJobWithModerateSuffering(log,*r1); 
     return; 
    } 
    DoTheJobQuicklyAndEasily(log,*r1,*r2); 
} 

Trường hợp chức năng acquireRes trả về một unique_ptr null khi tài nguyên thất bại trong việc khởi tạo:

std::unique_ptr<Res1Holder> acquireRes1() 
{ 
    try { 
    return std::unique_ptr<Res1Holder>(new Res1Holder()); 
    } 
    catch (Res1InitializationException& e) { 
    return std::unique_ptr<Res1Holder>(); 
    } 
} 

std::unique_ptr<Res2Holder> acquireRes2() 
{ 
    try { 
    return std::unique_ptr<Res2Holder>(new Res2Holder()); 
    } 
    catch (Res2InitializationException& e) { 
    return std::unique_ptr<Res2Holder>(); 
    } 
} 
+0

+1 Trong khi mã gốc trong câu hỏi là chính xác, điều này làm giảm thụt đầu dòng trong hàm 'DoTheJob'. Đó là một vấn đề của hương vị nhưng tôi nghĩ rằng điều này là dễ đọc hơn. –

+0

Tại sao có một unique_ptr xung quanh một nguồn tài nguyên đã được RAII? –

+0

@ DieterLücking: Làm cho nó một con trỏ cho phép tài nguyên được chuyển giữa các hàm hiệu quả và nó tự nhiên có giá trị null để chỉ ra rằng tài nguyên không thể có được. Sử dụng 'std :: unique_ptr' như trái ngược với việc sử dụng một con trỏ thô đảm bảo tài nguyên được tự động phát hành. –

1

Mã của bạn có vẻ tốt, vấn đề duy nhất tôi có thể tưởng tượng bạn có thể có với sự hoàn hảo, ngoại trừ được coi là không hiệu quả lắm. Nếu bạn có thể thay đổi mã thành:

void DoTheJob(Logger& log/*, some other params */) { 
    Res1HolderNoThrow r1(/* arguments for creating resource #1 */); 
    if(r1) { 
     Res2HolderNoThrow r2(/* arguments */); 
     if(r2) 
      DoTheJobQuicklyAndEasily(log, r1, r2); 
     else { 
      log.log("Can't obtain resource 2, that'll slowdown us a bit"); 
      DoTheJobWithModerateSuffering(log, r1); 
     } 
    } else { 
     log.log("Can't obtain resource 1, using fallback"); 
     DoTheJobTheSlowAndPainfulWay(log); 
    } 
} 

Bạn sẽ cần một đối tượng RAII khác không ngoại lệ nhưng có trạng thái và trả về trong toán tử bool() hoặc ở một nơi khác. Nhưng mã của bạn trông ít lỗi hơn dễ bị tôi, và tôi muốn sử dụng nó trừ khi bạn có vấn đề về hiệu suất hoặc cần phải tránh ngoại lệ.