2010-10-19 52 views
13

Tôi đang làm việc trên một số mã C++ nơi tôi có nhiều đối tượng quản lý bằng các phương pháp tư nhân nhưTôi có nên khai báo các phương pháp này không?

void NotifyFooUpdated(); 

đó gọi phương thức OnFooUpdated() trên thính giả của đối tượng này.

Lưu ý rằng chúng không sửa đổi trạng thái của đối tượng này, vì vậy chúng có thể được thực hiện về mặt kỹ thuật theo phương pháp const, mặc dù chúng thường sửa đổi trạng thái của toàn bộ hệ thống. Đặc biệt, các đối tượng nghe có thể gọi lại đối tượng này và sửa đổi nó.

Cá nhân tôi muốn để nguyên như chúng và không khai báo chúng const. Tuy nhiên, mã kiểm tra tĩnh QAC của chúng tôi cờ này như là một độ lệch, vì vậy tôi hoặc phải khai báo chúng const, hoặc tôi phải tranh luận lý do tại sao họ nên ở lại không const và nhận được một khoản trợ cấp cho độ lệch.

Đối số không tuyên bố những phương pháp này const là gì?
Hoặc tôi có nên theo dõi QAC và khai báo chúng const không?
Tôi có nên áp dụng một quan điểm nghiêm ngặt của địa phương bị hạn chế đối với đối tượng này hay xem xét toàn bộ hệ thống không?

Trả lời

3

Nói một cách lỏng lẻo rằng bạn có một lớp chứa: Một người quản lý có đầy đủ các nhà quan sát. Trong C và C++ bạn có thể có các thùng chứa const với các giá trị không phải là const. Xem xét nếu bạn loại bỏ một lớp gói:

list<Observer> someManager; 

void NotifyFooUpdated(const list<Observer>& manager) { ... } 

Bạn sẽ thấy không có gì lạ về một NotifyFooUpdated toàn cầu tham gia một danh sách const, vì nó không sửa đổi danh sách. Đối số const đó thực sự làm cho đối số phân tích cú pháp dễ chấp nhận hơn: Hàm này chấp nhận cả hai danh sách const và non-const. Tất cả chú thích const trên phiên bản phương thức lớp nghĩa là const *this.

Để giải quyết một quan điểm:

Nếu bạn không thể đảm bảo rằng đối tượng bạn gọi chức năng trên vẫn giữ nguyên trước và sau khi gọi hàm, bạn thường nên để lại mà như không const.

Điều đó chỉ hợp lý nếu người gọi có tham chiếu duy nhất cho đối tượng. Nếu đối tượng là toàn cầu (như trong câu hỏi ban đầu) hoặc trong môi trường luồng, thì constness của bất kỳ cuộc gọi nào không đảm bảo trạng thái của đối tượng không thay đổi trong suốt cuộc gọi. Một hàm không có tác dụng phụ và luôn trả về cùng một giá trị cho cùng một đầu vào là thuần túy. NotifyFooUpdate() rõ ràng là không thuần khiết.

+0

Tôi cho rằng bạn đang tranh luận ủng hộ việc khai báo phương pháp const? – starblue

+2

Nếu điều này được đưa ra trong một đánh giá mã, có lẽ tôi sẽ không phản đối một trong hai cách trừ khi có một số yếu tố khác khi chơi. Nếu tất cả mọi người đang truy cập vào toàn cầu và không có tham chiếu const được truyền xung quanh, nó thực sự không quan trọng. Tôi không đồng ý với giả thuyết rằng phân tích tĩnh có thể cho bạn biết nếu phương pháp này là const: Phân tích tĩnh cho bạn biết * có thể * là const * như được triển khai *, nhưng không biết bạn có dự định tăng cường chức năng sau này hay không với số liệu thống kê, hoặc thông điệp xếp hàng hoặc loại bỏ trùng lặp hoặc những gì có bạn). –

0

Tôi đoán bạn đang theo dõi HICPP hoặc nội dung tương tự. Những gì chúng tôi làm là nếu mã của chúng tôi vi phạm QACPP và chúng tôi nghĩ rằng đó là lỗi, chúng tôi lưu ý nó thông qua Doxygen (thông qua lệnh addtogroup để bạn có được danh sách dễ dàng), đưa ra lý do giải thích tại sao chúng tôi vi phạm và sau đó tắt cảnh báo qua lệnh //PRQA.

+0

Có, về mặt kỹ thuật nó tương tự ở đây, câu hỏi là phải làm gì và với lý do gì. Ngoài ra, tôi không phải là người cấp độ lệch, vì vậy tôi cần một sự hiểu biết đủ tốt về các vấn đề để tranh luận với đồng nghiệp của tôi. – starblue

2

Đối số không khai báo những phương pháp này const là gì?
Hoặc tôi có nên theo dõi QAC và khai báo chúng const không?
Tôi có nên áp dụng một quan điểm nghiêm ngặt của địa phương bị hạn chế đối với đối tượng này hay xem xét toàn bộ hệ thống không?

Điều bạn biết là đối tượng người quản lý này được gọi là thực hiện không thay đổi. Các đối tượng mà người quản lý sau đó gọi các chức năng trên có thể thay đổi hoặc có thể không. Bạn không biết điều đó.

Từ mô tả của bạn, tôi có thể hình dung thiết kế như vậy trong đó tất cả các đối tượng liên quan là const (và thông báo có thể được xử lý bằng cách ghi chúng vào bảng điều khiển). Nếu bạn không thực hiện chức năng này const, bạn cấm điều này. Nếu bạn làm cho nó const, bạn cho phép cả hai.

Tôi đoán đây là một đối số có lợi cho việc thực hiện điều này const.

+0

Tôi nghĩ điều này sẽ tương đối kỳ lạ, vì tất cả các nhà quản lý đều không phải là người có trách nhiệm. Cũng lưu ý rằng các phương thức này là riêng tư (tôi đã chỉnh sửa câu hỏi), và thường là các phương thức gọi chúng làm thay đổi trạng thái đối tượng. – starblue

3

Nếu người nghe được lưu trữ dưới dạng tập hợp các con trỏ, bạn có thể gọi phương thức không phải là const trên chúng ngay cả khi đối tượng của bạn là const.

Nếu hợp đồng là người nghe có thể cập nhật trạng thái của nó khi nhận được thông báo, thì phương pháp sẽ không phải là const.

Bạn đang nói rằng người nghe có thể gọi lại vào đối tượng và sửa đổi nó.Nhưng người nghe sẽ không thay đổi chính nó - vì vậy các cuộc gọi thông báo có thể được const nhưng bạn vượt qua một con trỏ không const để đối tượng của riêng bạn vào nó.

Nếu người nghe đã có con trỏ đó (nó chỉ nghe một điều) thì bạn có thể làm cho cả hai phương thức const, khi đối tượng của bạn nhận được sửa đổi là một tác dụng phụ. Điều gì đang xảy ra là:

Một cuộc gọi B B sửa đổi Kết quả là A.

Vì vậy, cuộc gọi B dẫn gián tiếp đến sự sửa đổi của riêng nó nhưng không phải là sự sửa đổi trực tiếp của bản thân.

Nếu đây là trường hợp cả hai phương pháp của bạn có thể và có thể là const.

+0

Vâng, về mặt kỹ thuật, có thể tạo ra phương pháp const, nhưng nó cũng tốt "thiết kế"? Bạn có muốn nói rằng nó luôn luôn là tốt để làm cho mọi thứ như const càng tốt? BTW, người nghe được lưu trữ như con trỏ không const và thường sẽ thay đổi bản thân. – starblue

1

Nếu bạn không thể đảm bảo rằng đối tượng mà bạn đã gọi hàm vẫn giữ nguyên trước và sau khi thực hiện cuộc gọi hàm, bạn thường nên để nguyên đó là không phải const. Hãy suy nghĩ về nó - bạn có thể viết một trình lắng nghe, chèn nó trong khi đối tượng không phải là const, và sau đó sử dụng hàm này để vi phạm const đúng đắn bởi vì bạn đã từng truy cập vào đối tượng đó khi nó không phải là const trong quá khứ. Sai rồi.

0

Lưu ý rằng họ không thay đổi trạng thái của đối tượng này, vì vậy họ có thể về mặt kỹ thuật được thực hiện phương pháp const, mặc dù họ thường thay đổi trạng thái của hệ thống như một toàn thể. Trong các trường hợp cụ thể, đối tượng nghe có thể gọi lại đối tượng này và sửa đổi .

Vì người nghe có thể thay đổi trạng thái, thì phương pháp này không nên là const. Từ những gì bạn đã viết, có vẻ như bạn đang sử dụng rất nhiều const_cast và gọi thông qua con trỏ.

+0

Không, không có 'const_cast' có liên quan, các đối tượng khác có con trỏ không const để tương tác với đối tượng này. – starblue

+0

Miễn là phương pháp của bạn đang thay đổi một cái gì đó, nó không nên được khai báo const. Đơn giản như thế. –

0

const tính chính xác có cách truyền đạt (cố ý mong muốn). bạn nên sử dụng const bất cứ nơi nào bạn có thể lấy đi với nó, trong khi const_cast và c-style-phôi nên được tạo tác của đối phó với mã khách hàng - không bao giờ trong mã của bạn, nhưng rất rất hiếm trường hợp ngoại lệ.

nếu void NotifyFooUpdated(); gọi listeners[all].OnFooUpdated() trong khi trên OnFooUpdated() không phải là const, thì bạn nên rõ ràng đủ điều kiện đột biến này. nếu mã của bạn là const chính xác trong suốt (mà tôi đang đặt câu hỏi), sau đó làm cho nó rõ ràng (thông qua khai báo phương pháp/người nghe truy cập) mà bạn đang mutating người nghe (thành viên), sau đó NotifyFooUpdated() nên đủ điều kiện như không const. vì vậy, bạn chỉ cần tuyên bố đột biến càng gần nguồn càng tốt và nó nên kiểm tra và const-đúng đắn sẽ tuyên truyền đúng cách.

0

Làm cho chức năng ảo const luôn là một quyết định khó khăn. Làm cho họ không phải là một cách dễ dàng. Một hàm listener sẽ là const trong nhiều trường hợp: nếu nó không thay đổi khía cạnh nghe (đối với đối tượng này). Nếu nghe một sự kiện sẽ khiến cho bên nghe tự hủy đăng ký (như một trường hợp chung), thì chức năng này sẽ không phải là const.

Mặc dù trạng thái bên trong của đối tượng có thể thay đổi trong cuộc gọi OnFooChanged, ở cấp độ giao diện, lần tiếp theo OnFooChanged được gọi, sẽ có kết quả tương tự. Mà làm cho nó const.

0

Khi sử dụng const trong các lớp học, bạn sẽ giúp người dùng của lớp đó biết cách lớp sẽ tương tác với dữ liệu. Bạn đang thực hiện một hợp đồng. Khi bạn có một tham chiếu đến một đối tượng const, bạn biết rằng bất kỳ cuộc gọi nào được thực hiện trên đối tượng đó sẽ không thay đổi trạng thái của nó. Độ chói của tham chiếu đó vẫn chỉ là một hợp đồng với người gọi. Đối tượng vẫn còn miễn phí để thực hiện một số hành động không phải là const trong nền sử dụng các biến có thể thay đổi.Điều này đặc biệt hữu ích khi lưu trữ thông tin.

Ví dụ bạn có thể có lớp học với phương pháp:

int expensiveOperation() const 
{ 
    if (!mPerformedFetch) 
    { 
     mValueCache = fetchExpensiveValue(); 
     mPerformedFetch = true; 
    } 
    return mValueCache; 
} 

phương pháp này có thể mất một thời gian dài để thực hiện lần đầu tiên, nhưng sẽ cache kết quả cho các cuộc gọi tiếp theo. Bạn chỉ cần đảm bảo rằng tệp tiêu đề khai báo các biến performFetch và valueCache là có thể thay đổi.

class X 
{ 
public: 
    int expensiveOperation() const; 
private: 
    int fetchExpensiveValue() const; 

    mutable bool mPerformedFetch; 
    mutable int mValueCache; 
}; 

Điều này cho phép đối tượng const thực hiện hợp đồng với người gọi và hành động giống như const trong khi làm việc thông minh hơn một chút trong nền.

Tôi khuyên bạn nên lớp học của bạn khai báo danh sách người nghe là có thể thay đổi và làm mọi thứ khác càng tốt. Theo như người gọi của đối tượng là có liên quan, đối tượng vẫn còn const và hành động theo cách đó.

1

Việc tôi thực hiện việc này là họ nên giữ nguyên không const. Điều này dựa trên nhận thức của tôi rằng trạng thái của đối tượng người quản lý, thực sự là tổng hợp các trạng thái của tất cả các đối tượng mà nó quản lý cộng với bất kỳ trạng thái nội tại nào, tức là, State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

Trong khi đóng gói trong mã nguồn có thể coi mối quan hệ thời gian chạy này. Dựa trên mô tả của bạn, tôi tin rằng trạng thái tổng hợp này phản ánh hành vi thời gian chạy của chương trình.

Để củng cố lập luận của mình: Tôi khẳng định rằng mã nên cố gắng phản ánh các hành vi thời gian chạy của chương trình theo mong muốn tuân thủ nghiêm ngặt ngữ nghĩa chính xác của việc biên dịch.

1

Đểconst, hoặc không đểconst: đó là câu hỏi.

Arguments cho const:

  • Các phương pháp trong câu hỏi không sửa đổi trạng thái của đối tượng.
  • Trình kiểm tra mã tĩnh của bạn đánh dấu sự thiếu độ lệch làm sai lệch, có thể bạn nên lắng nghe nó.

Lập luận chống lại const:

  • Các phương pháp thay đổi trạng thái của hệ thống như một toàn thể.
  • Đối tượng nghe tôi sửa đổi đối tượng.

Cá nhân tôi muốn rời khỏi nó là const, thực tế là nó có thể sửa đổi trạng thái của toàn bộ hệ thống là khá giống với tham chiếu con trỏ null. Đó là phương thức const, nó không sửa đổi đối tượng được đề cập, nhưng nó sẽ làm hỏng chương trình của bạn do đó sửa đổi trạng thái của toàn bộ hệ thống.

1

Có một số lý lẽ tốt cho chống const, ở đây vì vậy đây là quan điểm của tôi: -

Cá nhân, tôi muốn không có những "OnXXXUpdated" như là một phần của các lớp học quản lý của tôi. Tôi nghĩ rằng đây là lý do tại sao có một số nhầm lẫn là để thực hành tốt nhất. Bạn đang thông báo cho các bên quan tâm về điều gì đó và không biết trạng thái của đối tượng có thay đổi trong quá trình thông báo hay không. Hên xui. Điều gì hiển nhiên đối với tôi, là quá trình thông báo cho các bên quan tâm nên là một const.

Vì vậy, để giải quyết tình trạng khó xử này, đây là những gì tôi sẽ làm gì:

Loại bỏ các chức năng OnXXXXUpdated từ các lớp học quản lý của bạn.

Viết quản lý thông báo, đây là một nguyên mẫu, với các giả định sau:

"args" là một cơ sở hạng tùy ý để thông qua thông tin khi thông báo xảy ra

"Đại biểu" là một số loại của một con trỏ hàm (ví dụ: FastDelegate).

class Args 
{ 
}; 

class NotificationManager 
{ 
private: 
    class NotifyEntry 
    { 
    private: 
     std::list<Delegate> m_Delegates; 

    public: 
     NotifyEntry(){}; 
     void raise(const Args& _args) const 
     { 
      for(std::list<Delegate>::const_iterator cit(m_Delegates.begin()); 
       cit != m_Delegates.end(); 
       ++cit) 
       (*cit)(_args); 
     }; 

     NotifyEntry& operator += (Delegate _delegate) {m_Delegates.push_back(_delegate); return(*this); }; 
    }; // eo class NotifyEntry 

    std::map<std::string, NotifyEntry*> m_Entries; 

public: 
    // ctor, dtor, etc.... 

    // methods 
    void register(const std::string& _name);  // register a notification ... 
    void unRegister(const std::string& _name); // unregister it ... 

    // Notify interested parties 
    void notify(const std::string& _name, const Args& _args) const 
    { 
     std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name); 
     if(cit != m_Entries.end()) 
      cit.second->raise(_args); 
    }; // eo notify 

    // Tell the manager we're interested in an event 
    void listenFor(const std::string& _name, Delegate _delegate) 
    { 
     std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name); 
     if(cit != m_Entries.end()) 
      (*cit.second) += _delegate; 
    }; // eo listenFor 
}; // eo class NotifyManager 

Tôi đã để lại một số mã như bạn có thể nói, nhưng bạn có ý tưởng. Tôi tưởng tượng rằng Trình quản lý thông báo này sẽ là một singleton. Bây giờ, đảm bảo rằng Manager Thông báo được tạo ra từ rất sớm, phần còn lại của các nhà quản lý của bạn chỉ cần đăng ký thông báo của họ trong constructor của họ như thế này:

MyManager::MyManager() 
{ 
    NotificationMananger.getSingleton().register("OnABCUpdated"); 
    NotificationMananger.getSingleton().register("OnXYZUpdated"); 
}; 


AnotherManager::AnotherManager() 
{ 
    NotificationManager.getSingleton().register("TheFoxIsInTheHenHouse"); 
}; 

Bây giờ, khi quản lý của bạn cần phải thông báo cho các bên quan tâm, nó chỉ đơn giản là cuộc gọi thông báo:

MyManager::someFunction() 
{ 
    CustomArgs args; // custom arguments derived from Args 
    NotificationManager::getSingleton().notify("OnABCUpdated", args); 
}; 

Các lớp khác có thể nghe nội dung này.

Tôi đã nhận ra rằng tôi vừa nhập mẫu Observer, nhưng ý định của tôi là cho thấy vấn đề là cách thức những thứ này được nâng lên và liệu chúng có ở trạng thái const hay không. Bằng cách tóm tắt quá trình thông báo ra khỏi lớp mananager, người nhận thông báo được tự do sửa đổi lớp người quản lý đó. Chỉ cần không phải là người quản lý thông báo. Tôi nghĩ điều này là công bằng.

Bên cạnh đó, có một nơi duy nhất để nâng cao thông báo là tốt pracice imho, vì nó cung cấp cho bạn một nơi duy nhất mà bạn có thể theo dõi thông báo của bạn.

0

Const nghĩa là trạng thái của đối tượng không bị sửa đổi bởi hàm thành viên, không hơn, không kém. Nó không liên quan gì đến tác dụng phụ. Vì vậy, nếu tôi hiểu trường hợp của bạn một cách chính xác, trạng thái của đối tượng không thay đổi điều đó có nghĩa là hàm phải được khai báo const, trạng thái của các phần khác trong ứng dụng của bạn không liên quan gì đến đối tượng này. Ngay cả khi đôi khi trạng thái đối tượng có các đối tượng con không const, không phải là một phần của trạng thái logic của đối tượng (ví dụ: mutexes), các hàm vẫn phải được tạo thành const và các phần đó phải được khai báo có thể thay đổi.

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