C++ 最佳实践之 M&M rule

C++ 最佳实践之 M&M rule

M&M rule: mutable and metux(or shared_mutex(rwlock) or atomic …) go together!!

首先典型的问题的由来:在设计一个类的时候,我们希望一些方法是 const 的,
然后呢,有些场景,我们又希望所有方法是线程安全,于是使用 shared_mutex 等,
但是 const 方法怎么办 ?不考虑一些 hack的方式,例如 const_cast、直接获取地址等,
— 于是就会使用 mutable 修饰 shared_mutex,这样 const 方法可以正常使用 shared_mutx
的对象的。

另外,

  • 在使用 lambda 表达式(closure)的时候,也可以使用 mutable 修饰,这样通过拷贝方式捕获的变量,也可以在 closure 中修改,但是只是修改了 closure 中的,外面的不受影响!当然如果不用修改,最好是 immutable 的!
  • 然后 C++98 也是可以使用 mutable,例如 gcc、vc 等都是支持的。

Guideline: Remember the “M&M rule”: For a member variable, mutable and mutex (or atomic) go together.

(1) For a member variable, mutable implies mutex (or equivalent): A mutable member variable is presumed to be a mutable shared variable and so must be synchronized internally—protected with a mutex, made atomic, or similar.

(2) For a member variable, mutex (or similar synchronization type) implies mutable: A member variable that is itself of a synchronization type, such as a mutex or a condition variable, naturally wants to be mutable, because you will want to use it in a non-const way (e.g., take a std::lock_guard) inside concurrent const member functions.


See also

One more circular buffer(normal queue)

One more circular buffer(normal queue)

Sometimes we prefer to use circular buffer: “like CircularBuffer, but NOT use size and next index, but use front and rear”, so an impletametation here.

See also
Write a CircularBuffer
std::queue
Full impletametation: circularqueue.hpp

/**
 * Var capacity circular queue.
 * NOTE:
 * - Like CircularBuffer, but if full, enque(push) will FAIL!
 * - And like CircularBuffer, but NOT use size and next index, but use front
 *   and rear.
 * - When use in multi-threads, should add lock or some other to sync.
 * @tparam T data type.
 * @sa CircularBuffer
 * @todo
 * - Add full CircularBuffer like features.
 * - Add iterators.
 */
template<typename T>
class CircularQueue {
public:
    /**
     * Ctor.
     * @param capacity the buffer capacity, should >= 1.
     * @throw std::logic_error when capacity invalid.
     */
    CircularQueue(int32_t capacity = 1, const T& initValue = T{});
    /**
     * Test whether container is empty
     * @return True if queue is empty, false otherwise.
     */
    inline bool empty() const noexcept;
    /**
     * Test whether container is empty
     * @return True if queue if full, false otherwise.
     */
    inline bool full() const noexcept;
    /// @return data size.
    inline int32_t size() const noexcept;
    /// @return queue capacity.
    inline int32_t capacity() const noexcept;
    /**
     * Inserts a new element at the end of the queue, after its current last
     * element. The content of this new element is initialized to value.
     * @param value Value to which the inserted element is initialized.
     * Member type value_type is the type of the elements in the container
     * (defined as an alias of the first class template parameter, T).
     */
    bool push(const T& value) noexcept;
    bool push(T&& value) noexcept;
    bool pop() noexcept;
    bool pop(T& value) noexcept;
    /// @throw std::logic_error when queue empty.
    T pop_front();
    /**
     * Get queue head(front) when not empty.
     * @return queue head(front) when not empty.
     * @throw std::logic_error when empty.
     */
    const T& front() const;
    T& front();
private:
    /// Pop index(current or next readable), init as empty.
    int32_t currRFront{ -1 };
    int32_t lastWRear{ -1 };///< Push index(last writeable), init as empty
    int32_t capa{ 1 };///< The buffer capacity, > 0.
    std::vector<T> buffer;///< The internal buffer.
};