2016-09-15 24 views
5

Những gì tôi có cho đến nay là:Sử dụng weak_ptr để thực hiện mô hình Observer

Observer.h

class Observer 
{ 
public: 
    ~Observer(); 
    virtual void Notify() = 0; 
protected: 
    Observer(); 
}; 

class Observable 
{ 
public: 
    ~Observable(); 
    void Subscribe(std::shared_ptr<Observer> observer); 
    void Unsubscribe(std::shared_ptr<Observer> observer); 
    void Notify(); 
protected: 
    Observable(); 
private: 
    std::vector<std::weak_ptr<Observer>> observers; 
}; 

Observer.cpp

void Observable::Subscribe(std::shared_ptr<Observer> observer) 
{ 
    observers.push_back(observer); 
} 

void Observable::Unsubscribe(std::shared_ptr<Observer> observer) 
{ 
    ??? 
} 

void Observable::Notify() 
{ 
    for (auto wptr : observers) 
    { 
     if (!wptr.expired()) 
     { 
      auto observer = wptr.lock(); 
      observer->Notify(); 
     } 
    } 
} 

(de/nhà thầu được thực hiện ở đây nhưng có sản phẩm nào , vì vậy tôi đã bỏ chúng ra)

Điều tôi đang gặp phải là cách triển khai thủ tục Hủy đăng ký. Tôi đi qua xóa - loại bỏ thành ngữ, nhưng tôi hiểu rằng nó sẽ không làm việc "ra khỏi hộp" với cách tôi đã thiết lập quan sát của tôi. Làm thế nào để kiểm tra các yếu tố weak_ptr trong vector quan sát sao cho tôi có thể loại bỏ Trình quan sát mong muốn?

Tôi cũng đang tìm một số lời khuyên về loại tham số nên dành cho các thủ tục Bỏ đăng ký của tôi. Sẽ tốt hơn nếu sử dụng std::shared_ptr<Observer>& hoặc const std::shared_ptr<Observer>&, vì chúng tôi sẽ không sửa đổi nó? Tôi thực sự không muốn có Observables sở hữu các quan sát viên của họ, vì nó có vẻ phản bội ý định của mô hình, và chắc chắn không phải là cách tôi muốn cấu trúc phần còn lại của dự án mà cuối cùng sẽ sử dụng mẫu . Điều đó nói rằng, một lớp bảo mật/tự động hóa bổ sung mà tôi đang xem xét là để các nhà quan sát lưu trữ một véc-tơ gương của weak_ptr. Một Observer trên đường ra sau đó có thể hủy đăng ký khỏi tất cả các Observables mà nó đã đăng ký, và một Observable trên đường đi có thể xóa bỏ tham chiếu ngược về chính nó từ mỗi Quan sát viên quan sát nó. Rõ ràng hai lớp sẽ là bạn trong một kịch bản như vậy.

Trả lời

2

Bạn có thể sử dụng std::remove_if với std::erase như thế này:

void Observable::Unsubscribe(std::shared_ptr<Observer> observer) 
{ 
    std::erase(
     std::remove_if(
      this->observers.begin(), 
      this->observers.end(), 
      [&](const std::weak_ptr<Observer>& wptr) 
      { 
       return wptr.expired() || wptr.lock() == observer; 
      } 
     ), 
     this->observers.end() 
    ); 
} 

Bạn thực sự cần phải vượt qua observer như const std::shared_ptr<Observer>&.

+3

Lưu ý rằng 'std :: remove_if' không ** không ** xóa các phần tử khỏi vùng chứa. Bạn phải sử dụng 'container.erase' cùng với nó. Tìm kiếm thành phần xóa-xóa. – Nawaz

+0

... mặc dù trong trường hợp này, 'std :: remove_if' sẽ là (có thể) không hiệu quả; 'std :: find_if' cùng với' .erase' sẽ hoạt động tốt hơn (hoặc ít nhất * ngữ nghĩa * chính xác). – Nawaz

+1

Đã thêm wptr.expired() kiểm tra an toàn và xóa các quan sát viên đã chết. – M2tM

1

Điều tôi đang gặp phải là cách triển khai quy trình hủy đăng ký.

Tôi khuyên bạn nên lưu trữ người quan sát trong danh sách std :: vì các trình vòng lặp của nó không bị vô hiệu khi sửa đổi vùng chứa. Sau đó, trong đăng ký trong quan sát bạn lưu trữ iterator cho nó và trong unsubscribe bạn sử dụng iterator để loại bỏ các yếu tố. Nhưng tất nhiên bạn có thể sử dụng std :: vector và std :: remove_if như được đề xuất trong câu trả lời khác.

Bây giờ, về tất cả những thứ * _ptr đó. Trong C++ RAII là bạn của bạn nên sử dụng nó. Loại bỏ phương thức hủy đăng ký công khai. Thay vào đó, người quan sát phải hủy đăng ký chính nó trong destructor của nó. Điều này đơn giản hóa mọi thứ rất nhiều: không còn khóa các con trỏ yếu nữa: nếu người quan sát đã bị xóa, thì nó không có trong danh sách. Chỉ cần đừng quên bảo vệ danh sách người quan sát bằng một mutex nếu bạn có ứng dụng đa luồng. Nếu bạn sử dụng thiết kế này, thì Observable sẽ chỉ cần các con trỏ đơn giản tới các Trình quan sát và sẽ không có yêu cầu nào về cách mà các Trình theo dõi phải được lưu trữ.

class Observer { 
public: 
    void subscribe(std::function<void()> unsubscribe) { 
     unsubscribe_ = std::move(unsubscribe); 
    } 

    virtual ~Observer() { 
     unsubscribe_(); 
    } 
private: 
    std::function<void()> unsubscribe_; 
}; 

class Observable { 
public: 
    void subscribe(Observer* observer) { 
     std::lock_guard<std::mutex> lock(observablesMutex_); 
     auto itr = observers_.insert(observers_.end(), observer); 
     observer->subscribe([this, itr]{ 
      std::lock_guard<std::mutex> lock(observablesMutex_); 
      observers_.erase(itr); 
     }); 
    } 

private: 
    std::list<Observer*> observers_; 
    std::mutex observablesMutex_; 
}; 

Lưu ý: đối với mã này Người quan sát phải luôn bị hủy trước khi có thể quan sát.


Cập nhật: nếu bạn sử dụng C++ lambdas, bạn có thể thấy rằng có std :: chức năng như người quan sát thuận tiện hơn trong nhiều trường hợp hơn là có phân cấp lớp đặc biệt.Trong trường hợp này, API của bạn có thể giống như sau:

class Handle { 
public: 
    explicit Handle(std::function<void()> onDestroy) 
     : onDestroy_(std::move(onDestroy)) {} 

    Handle(const Handle&) = delete; 

    Handle(Handle&&) = default; 

    virtual ~Observer() { 
     onDestroy_(); 
    } 
private: 
    std::function<void()> onDestroy_; 
}; 

class Observable { 
public: 
    Handle subscribe(std::function<void()> observer) { 
     std::lock_guard<std::mutex> lock(observablesMutex_); 
     auto itr = observers_.insert(observers_.end(), observer); 
     return {[this, itr]{ 
      std::lock_guard<std::mutex> lock(observablesMutex_); 
      observers_.erase(itr); 
     }}; 
    } 

private: 
    std::list<std::function<void()>> observers_; 
    std::mutex observablesMutex_; 
}; 
Các vấn đề liên quan