1


This is a class which contains image data.

class MyMat
{
public:
    int width, height, format;
    uint8_t *data;
}

I want to design MyMat with automatic memory management. The image data could be shared among many objects. Common APIs which I'm going to design:

+) C++ 11

+) Assignment : share data

MyMat a2(w, h, fmt);
.................
a2 = a1; 

+) Accessing data should be simple and short. Can use raw pointer directly.

In general, I want to design MyMat like as OpenCV cv::Mat

Could you suggest me a proper design ?

1) Using std::vector<uint8_t> data
I have to write some code to remove copy constructor and assignment operator because someone can call them and causes memory copy. The compiler must support copy ellision and return value optimization. Always using move assignment and passing by reference are inconvenient

a2 = std::move(a1)
void test(MyMat &mat)
std::queue<MyMat> lists;
lists.push_back(std::move(a1))
..............................

2) Use share_ptr<uint8_t> data
Following this guideline http://www.codingstandard.com/rule/17-3-4-do-not-create-smart-pointers-of-array-type/, we shouldn't create smart pointers of array type.

3) Use share_ptr< std::vector<uint8_t> > data
To access data, use *(a1.data)[0], the syntax is very inconvenient

4) Use raw pointer, uint8_t *data
Write proper constructor and destructor for this class. To make automatic memory management, use smart pointer.

share_ptr<MyMat> mat
std::queue< share_ptr<MyMat> > lists;
2

3 Answers 3

3

Matrix classes are normally expected to be a value type with deep copying. So, stick with std::vector<uint8_t> and let the user decide whether copy is expensive or not in their specific context.


Instead of raw pointers for arrays prefer std::unique_ptr<T[]> (note the square brackets).

Sign up to request clarification or add additional context in comments.

Comments

3

std::array - fixed length in-place buffer (beautified array)
std::vector - variable length buffer
std::shared_ptr - shared ownership data
std::weak_ptr - expiring view on shared data
std::unique_ptr - unique ownership
std::string_view, std::span, std::ref, &, * - reference to data with no assumption of ownership

Simplest design is to have a single owner and RAII-forced life time ensuring everything that needs to be alive at certain time is alive and needs no other ownership, so generally I'd see if I could live std::unique_ptr<T> before complicating further (unless I can fit all my data on the stack, then I don't even need a unique_ptr).

On a side note - shared pointers are not free, they need dynamic memory allocation for the shared state (two allocations if done incorrectly :) ), whereas unique pointers are true "zero" overhead RAII.

Comments

1

Matrixes should use value semantics, and they should be nearly free to move.

Matrixes should support a view type as well.

There are two approaches for a basic Matrix that make sense.

First, a Matrix type that wraps a vector<T> with a stride field. This has an overhead of 3 instead of 2 pointers (or 1 pointer and a size) compared to a hand-rolled one. I don't consider that significant; the ease of debugging a vector<T> etc makes it more than worth that overhead.

In this case you'd want to write a separate MatrixView.

I'd use CRTP to create a common base class for both to implement operator[] and stride fields.


A distinct basic Matrix approach is to make your Matrix immutable. In this case, the Matrix wraps a std::shared_ptr<T const> and a std::shared_ptr<std::mutex> and (local, or stored with the mutex) width, height and stride field.

Copying such a Matrix just duplciates handles.

Modifying such a Matrix causes you to acquire the std::mutex, then check that shared_ptr<T const> has a use_count()==1. If it does, you cast-away const and modify the data referred to in the shared_ptr. If it does not, you duplicate the buffer, create a new mutex, and operate on the new state.

Here is a copy on write matrix buffer:

template<class T>
struct cow_buffer {
  std::size_t rows() const { return m_rows; }
  std::size_t cols() const { return m_cols; }

  cow_buffer( T const* in, std::size_t rows, std::size_t cols, std::size_t stride ) {
    copy_in( in, rows, cols, stride );
  }

  void copy_in( T const* in, std::size_t rows, std::size_t cols, std::size_t stride ) {
    // note it  isn't *really* const, this matters:
    auto new_data = std::make_shared<T[]>( rows*cols );
    for (std::size_t i = 0; i < rows; ++i )
      std::copy( in+i*stride, in+i*m_stride+m_cols, new_data.get()+i*m_cols );
    m_data = new_data;
    m_rows = rows;
    m_cols = cols;
    m_stride = cols;
    m_lock = std::make_shared<std::mutex>();
  }

  template<class F>
  decltype(auto) read( F&& f ) const {
    return std::forward<F>(f)( m_data.get() );
  }
  template<class F>
  decltype(auto) modify( F&& f ) {
    auto lock = std::unique_lock<std::mutex>(*m_lock);
    if (m_data.use_count()==1) {
      return std::forward<F>(f)( const_cast<T*>(m_data.get()) );
    }
    auto old_data = m_data;
    copy_in( old_data.get(), m_rows, m_cols, m_stride );
    return std::forward<F>(f)( const_cast<T*>(m_data.get()) );
  }
  explicit operator bool() const { return m_data && m_lock; }
private:
  std::shared_ptr<T> m_data;
  std::shared_ptr<std::mutex> m_lock;
  std::size_t m_rows = 0, m_cols = 0, m_stride = 0;
};

something like that.

The mutex is required to ensure synchonization between multiple threads who are sole owners modifying m_data and the data from the previous write not being synchronzied with the current one.

1 Comment

Thanks ! I only need a simple MyMat object which contains continuous data. User code will not access matrix data across threads or they must use their own mutex.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.