2015-10-12 27 views
5

Python chúng tôi codebase có mã số liệu liên quan đến trông như thế này:Thực hành tốt nhất + cú pháp để thực hiện một "contextmanager" trong C++

class Timer: 
    def __enter__(self, name): 
     self.name = name 
     self.start = time.time() 

    def __exit__(self): 
     elapsed = time.time() - self.start 
     log.info('%s took %f seconds' % (self.name, elapsed)) 

... 

with Timer('foo'): 
    do some work 

with Timer('bar') as named_timer: 
    do some work 
    named_timer.some_mutative_method() 
    do some more work 

Trong thuật ngữ Python, bộ đếm thời gian là một contextmanager.

Bây giờ, chúng tôi muốn thực hiện điều tương tự trong C++, với cú pháp đẹp như nhau. Thật không may, C++ không có with. Vì vậy, "rõ ràng" thành ngữ sẽ là (RAII cổ điển)

class Timer { 
    Timer(std::string name) : name_(std::move(name)) {} 
    ~Timer() { /* ... */ } 
}; 

if (true) { 
    Timer t("foo"); 
    do some work 
} 
if (true) { 
    Timer named_timer("bar"); 
    do some work 
    named_timer.some_mutative_method(); 
    do some more work 
} 

Nhưng điều này là cực kỳ xấu xí muối cú pháp: đó là nhiều dòng dài hơn nó cần phải được, chúng tôi đã giới thiệu một tên t cho timer "vô danh" của chúng tôi (và mã phá vỡ âm thầm nếu chúng ta quên tên đó) ... nó chỉ là xấu xí.

Một số thành ngữ cú pháp mà mọi người đã sử dụng để xử lý "contextmanagers" trong C++ là gì?


Tôi đã nghĩ đến ý tưởng lạm dụng này, làm giảm dòng-count nhưng không thoát khỏi tên t:

// give Timer an implicit always-true conversion to bool 
if (auto t = Timer("foo")) { 
    do some work 
} 

Hoặc quái vật kiến ​​trúc này, mà tôi don' thậm chí không tin tưởng bản thân mình để sử dụng một cách chính xác:

Timer("foo", [&](auto&) { 
    do some work 
}); 
Timer("bar", [&](auto& named_timer) { 
    do some work 
    named_timer.some_mutative_method(); 
    do some more work 
}); 

nơi các nhà xây dựng của Timer actuall y gọi lambda đã cho (với đối số *this) và ghi nhật ký tất cả trong một lần.

Tuy nhiên, những ý tưởng này có vẻ như là "thực tiễn tốt nhất". Giúp tôi ra đây!


Một cách khác để cụm từ câu hỏi có thể là: Nếu bạn được thiết kế std::lock_guard từ đầu, làm thế nào bạn sẽ làm điều đó để loại bỏ càng nhiều càng tốt soạn sẵn? lock_guard là một ví dụ hoàn hảo của bối cảnh ngữ cảnh: đó là một tiện ích, đó là bản chất RAII, và bạn hầu như không bao giờ muốn bận tâm đặt tên nó.

+2

Bạn có thể tạo khối không giới thiệu 'if (true)' – Jarod42

+1

Giải pháp chung chỉ là raii. Là câu hỏi cụ thể của bạn làm thế nào để viết một bộ đếm thời gian để in thời gian trôi qua ở cuối? – Barry

+0

Tôi thực sự nghĩ rằng giải pháp lambda của bạn là tốt đẹp. – JorenHeit

Trả lời

1

Bạn không cần if(true), C++ có "phạm vi vô danh" mà có thể được sử dụng để hạn chế cuộc đời của một phạm vi trong nhiều cùng là như Python của with hoặc C# s using (tốt, C# cũng có phạm vi mang tính chất quá).

Giống như vậy:

doSomething(); 
{ 
    Time timer("foo"); 
    doSomethingElse(); 
} 
doMoreStuff(); 

Chỉ cần sử dụng trần xoăn-dấu ngoặc đơn.

Tuy nhiên, tôi không đồng ý với ý tưởng của bạn về việc sử dụng ngữ nghĩa RAII đối với mã công cụ như thế này vì hàm hủy là timer không có tầm thường và có tác dụng phụ theo thiết kế. Nó có thể là xấu xí và lặp đi lặp lại, nhưng tôi cảm thấy một cách rõ ràng gọi là các phương pháp startTimer, stopTimerprintTimer làm cho chương trình "đúng" hơn và tự ghi lại tài liệu. Tác dụng phụ là xấu, m'key?

+0

Tôi không hiểu tại sao bạn không nghĩ RAII là phù hợp ở đây. Tất cả các RAII destructors sẽ là không tầm thường vì họ phải phát hành một số tài nguyên. Nếu nó tầm thường thì lớp RAII sẽ dư thừa. – pcarter

+1

@pcarter Trên thực tế, tôi nghĩ Đại làm nên một điểm tốt. Trong trường hợp này, destructor đang ghi nhật ký, có nghĩa là io, có nghĩa là nó thực sự có thể ném. Ném destructors là xấu. –

+0

Một thử-catch sẽ ngăn chặn một ngoại lệ từ thoát destructor. Và nó chỉ ra rằng tăng cung cấp một bộ đếm thời gian dựa trên RAII (các bản ghi), xem: http://www.boost.org/doc/libs/1_59_0/libs/timer/doc/cpu_timers.html. Có lẽ OP chỉ có thể sử dụng lớp của boost. – pcarter

1

Chỉnh sửa: Sau khi đọc nhận xét của Đại kỹ hơn, và suy nghĩ thêm một chút, tôi nhận ra đây là một lựa chọn không tốt cho C++ RAII. Tại sao?Bởi vì bạn đang đăng nhập vào destructor, điều này có nghĩa là bạn đang làm io, và io có thể ném. C++ destructors không nên phát ra ngoại lệ. Với python, việc viết ném __exit__ cũng không hẳn là tuyệt vời, nó có thể khiến bạn bỏ ngoại lệ đầu tiên trên sàn. Nhưng trong python, bạn chắc chắn biết liệu mã trong trình quản lý ngữ cảnh có gây ra ngoại lệ hay không. Nếu nó gây ra một ngoại lệ, bạn chỉ có thể bỏ qua đăng nhập của bạn trong __exit__ và vượt qua ngoại lệ. Tôi để lại câu trả lời ban đầu của tôi dưới đây trong trường hợp bạn có một người quản lý ngữ cảnh mà không có nguy cơ ném vào lối ra.

Phiên bản C++ dài hơn phiên bản python 2 dòng, một dòng cho mỗi dấu ngoặc nhọn. Nếu C++ chỉ dài hơn hai dòng so với python, thì nó hoạt động tốt. Trình quản lý ngữ cảnh được thiết kế cho điều cụ thể này, RAII tổng quát hơn và cung cấp một bộ siêu chức năng nghiêm ngặt. Nếu bạn muốn biết thực hành tốt nhất, bạn đã tìm thấy nó: có một phạm vi vô danh và tạo đối tượng ngay từ đầu. Đây là thành ngữ. Bạn có thể tìm thấy nó xấu xí đến từ python, nhưng trong thế giới C++ nó chỉ là tốt. Cách tương tự một người nào đó từ C++ sẽ tìm thấy các trình quản lý ngữ cảnh xấu xí trong các tình huống nhất định. FWIW Tôi sử dụng cả hai ngôn ngữ chuyên nghiệp và điều này không làm phiền tôi chút nào.

Điều đó nói rằng, tôi sẽ cung cấp một cách tiếp cận rõ ràng hơn cho người quản lý ngữ cảnh ẩn danh. Cách tiếp cận của bạn với việc xây dựng Timer với lambda và ngay lập tức cho phép nó phá hủy là khá lạ, vì vậy bạn có quyền nghi ngờ. Một cách tiếp cận tốt hơn:

template <class F> 
void with_timer(const std::string & name, F && f) { 
    Timer timer(name); 
    f(); 
} 

Cách sử dụng:

with_timer("hello", [&] { 
    do something; 
}); 

này tương đương với người quản lý bối cảnh mang tính chất theo nghĩa là không ai trong số các phương pháp hẹn giờ có thể được gọi là khác so với xây dựng và phá hủy. Ngoài ra, nó sử dụng lớp "bình thường", vì vậy bạn có thể sử dụng lớp khi bạn cần một trình quản lý ngữ cảnh có tên và chức năng này theo cách khác. Bạn rõ ràng có thể viết with_lock_guard theo cách tương tự. Nó thậm chí còn tốt hơn vì lock_guard không có bất kỳ chức năng thành viên nào mà bạn bỏ lỡ.

Tất cả những gì đã nói, tôi có sử dụng with_lock_guard hoặc phê duyệt mã được viết bởi một đồng đội được thêm vào tiện ích như vậy không? Không. Một hoặc hai dòng mã bổ sung không quan trọng; chức năng này không thêm đủ tiện ích để biện minh cho sự tồn tại của chính nó. YMMV.

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