用C ++实现N维矩阵

时间:2014-04-08 23:46:43

标签: c++ matrix operator-overloading n-dimensional

我想用C ++实现我自己的N维Matrix类。但是,我仍然坚持如何实现它,特别是在实现操作符以访问该矩阵中的元素时。

有什么建议吗?

1 个答案:

答案 0 :(得分:1)

我实施了一个非常简单的版本,已经过几个月的测试,所以我觉得它非常可靠:https://github.com/Sheljohn/ndArray

它完全是基于标准C ++ 11的(使用g ++编译时使用-std=c++0x);没有外部依赖,应该是平台无关的(未经过测试)。它确实提供了与Matlab的兼容性,但您可以轻松删除特定于mxArray的部分。如果您选择使用它并希望扩展它,请告诉我;当前版本旨在提供最低限度,但我很乐意为任何扩展/错误提供帮助。


你去,这应该编译得很好。文档在评论中或在Github上。

ndArray.h

#ifndef __ND_ARRAY__
#define __ND_ARRAY__

//================================================
// @file         ndArray.h
// @author       Jonathan Hadida
// @contact      cf https://github.com/Sheljohn/MexArray
//================================================

#include <iostream>
#include <cstdlib>
#include <cstdarg>
#include <cstdio>

#include <exception>
#include <stdexcept>

#include <memory>
#include <algorithm>
#include <type_traits>
#include <initializer_list>

// Protect 1D access (comment for faster access)
#define ND_ARRAY_SAFE_ACCESS
#ifdef ND_ARRAY_SAFE_ACCESS
#define ND_ARRAY_PROTECT(k,n) (k % n)
#else
#define ND_ARRAY_PROTECT(k,n) k
#endif



        /********************     **********     ********************/
        /********************     **********     ********************/



/**
 * Convert nd coordinates to 1d index.
 * Two main variants are provided:
 * - Taking an ARRAY as coordinates (size input by template)
 * - Taking a VA_LIST as a list of coordinate inputs (cf operator() below).
 */
template <unsigned N>
unsigned sub2ind( const unsigned *subs, const unsigned *size, const unsigned *strides )
{
    register unsigned ind = 0;

    for (unsigned i = 0; i < N; ++i) 
        ind += ND_ARRAY_PROTECT(subs[i],size[i]) * strides[i];

    return ind;
}

template <unsigned N>
unsigned sub2ind( va_list& vl, const unsigned *size, const unsigned *strides )
{
    register unsigned ind = 0;

    for (unsigned i = 1; i < N; ++i) 
        ind += ND_ARRAY_PROTECT(va_arg(vl,unsigned),size[i]) * strides[i];

    va_end(vl); return ind;
}

template <> inline unsigned 
sub2ind<0>( const unsigned*, const unsigned*, const unsigned* )
    { return 0; }
template <> inline unsigned 
sub2ind<1>( const unsigned *subs, const unsigned*, const unsigned* )
    { return *subs; }
template <> inline unsigned 
sub2ind<2>( const unsigned *subs, const unsigned *size, const unsigned* )
    { return (subs[0] + subs[1]*size[0]); }

// ------------------------------------------------------------------------

/**
 * Simple singleton.
 */
template <typename T> struct singleton { static T instance; };
template <typename T> T singleton<T>::instance = T();

// ------------------------------------------------------------------------

/**
 * Dummy deleter functor.
 * This litterally does nothing to the input pointer; it can be used 
 * safely with shared pointers for either statically allocated memory 
 * (eg fixed-size arrays) or externally managed memory (eg Matlab in/out).
 */
template <typename T>
struct no_delete
{ inline void operator() ( T* ptr ) const {} };



        /********************     **********     ********************/
        /********************     **********     ********************/



/**
 * n-dimensional array.
 * NOTE: T can be CONST (underlying elements non-assignable), 
 * or NON-CONST (underlying elements assignable, suitable for 
 * owned memory allocation for instance).
 */
template <typename T, unsigned N>
class ndArray
{
public:

    typedef T value_type;
    typedef T* pointer;
    typedef T& reference;

    typedef typename std::add_const<T>::type const_value;
    typedef const_value* const_pointer;
    typedef const_value& const_reference;

    typedef std::shared_ptr<value_type> shared;
    typedef ndArray<T,N> self;



    // Constructors
    ndArray() { reset(); }
    ndArray( pointer ptr, const unsigned *size, bool manage ) { assign(ptr,size,manage); }


    // Copy constructor
    ndArray( const self& other ) { operator=(other); }
    self& operator= ( const self& other );

    // Check pointer validity
    inline bool empty() const { return !((bool) m_data); }
    inline operator bool() const { return m_data; }

    // Print array dimensions
    void info() const;



    // ------------------------------------------------------------------------



    // Clear contents
    void clear();
    void reset();


    // Assign a pointer or another instance
    void assign( pointer ptr, const unsigned *size, bool manage );
    void assign( const self& other ) { operator=(other); }


    // Swap contents with another array
    void swap( self& other );


    // Copy from another array
    template <typename U>
    void copy( const ndArray<U,N>& other );



    // ------------------------------------------------------------------------



    // 1D access
    inline reference operator[] ( unsigned n ) const 
        { return data()[ ND_ARRAY_PROTECT(n,m_numel) ]; }

    // ND access
    reference operator() ( const unsigned *subs ) const
        { return data()[ sub2ind<N>(subs, m_size, m_strides) ]; }

    reference operator() ( std::initializer_list<unsigned> subs ) const
        {
#ifdef ND_ARRAY_SAFE_ACCESS
            if ( subs.size() != N ) 
                throw std::length_error("Invalid coordinates length.");
#endif
            return data()[ sub2ind<N>(subs.begin(), m_size, m_strides) ];
        }

    // Coordinates access
    reference operator() ( unsigned i, ... ) const
        { 
            va_list vl; va_start(vl,i); 
            return data()[ i+sub2ind<N>(vl, m_size, m_strides) ]; 
        }


    // Access data directly
    inline const_pointer cdata() const { return m_data.get(); }
    inline pointer data() const { return m_data.get(); }

    // Iterators
    inline const_pointer cbegin() const { return data(); }
    inline const_pointer cend() const { return data() + m_numel; }

    inline pointer begin() const { return data(); }
    inline pointer end() const { return data() + m_numel; }



    // ------------------------------------------------------------------------



    // Dimensions
    inline const unsigned* size() const { return m_size; }
    inline unsigned size( unsigned n ) const { return m_size[ n % N ]; }
    inline const unsigned* strides() const { return m_strides; }
    inline unsigned stride( unsigned n ) const { return m_strides[ n % N ]; }
    inline unsigned numel() const { return m_numel; }
    inline unsigned ndims() const { return N; }



protected:

    void assign_shared( pointer ptr, bool manage );

    unsigned m_numel;
    unsigned m_size[N];
    unsigned m_strides[N];

    shared m_data;
};



        /********************     **********     ********************/
        /********************     **********     ********************/



// Include implementation
#include "ndArray.hpp"

#endif

ndArray.hpp

/**
 * Assignment operator.
 * Performs a shallow copy of the other instance.
 * For deep-copies, see copy() below.
 */
template <typename T, unsigned N>
ndArray<T,N>& ndArray<T,N>::operator=( const self& other )
{
    if ( other.m_data != m_data )
    {
        // Clear current instance first
        clear();

        // Copy data from other
        m_data  = other.m_data;
        m_numel = other.m_numel;
        std::copy_n( other.m_size, N, m_size );
        std::copy_n( other.m_strides, N, m_strides );
    }

    return *this;
}

// ------------------------------------------------------------------------

/**
 * Performs a deep-copy of another instance with possibly 
 * different value-type. Deep copies are allowed only if 
 * the pointer type is non-const (otherwise no assignment 
 * is possible). 
 *
 * To perform the copy, a new memory allocation is requested 
 * to store as many values as other.m_numel; the current 
 * instance takes ownership of this new memory. 
 * 
 * Note that subsequent shallow copies (see assignment operator)
 * will simply share this ownership (reference counting). 
 * Note also that this code might generate warnings because of
 * the value-cast performed on the values of other.
 */
template <typename T, unsigned N>
template <typename U>
void ndArray<T,N>::copy( const ndArray<U,N>& other )
{
    if ( !std::is_const<T>::value )
    {
        // Create new allocation only if necessary
        if ( other.numel() == m_numel )
        {
            // Otherwise simply copy dimensions
            std::copy_n( other.size(), N, m_size );
            std::copy_n( other.strides(), N, m_strides );
        }
        else
            assign( new T[ other.numel() ], other.size(), true );

        // Copy data
        auto dst = begin(); auto src = other.cbegin();
        for ( ;src != other.cend(); ++src, ++dst )  *dst = (T) *src;
    }
    else 
        throw std::logic_error("Const values cannot be assigned!");
}

// ------------------------------------------------------------------------

/**
 * Reset shared pointer.
 * This will trigger the deletion of the underlying memory if
 * m_data is unique (m_data.use_count() == 1). Note that if the
 * data was assigned with 'manage' set to false (see below), 
 * the deleter(no_delete functor) will NOT release the memory.
 */
template <typename T, unsigned N>
void ndArray<T,N>::clear()
{
    m_data.reset();
}

// ------------------------------------------------------------------------

/**
 * More thorough cleanup. Calls clear() (see above), and sets 
 * all the rest to 0.
 */
template <typename T, unsigned N>
void ndArray<T,N>::reset()
{
    clear();
    m_numel = 0;
    std::fill_n( m_size, N, 0 );
    std::fill_n( m_strides, N, 0 );
}

// ------------------------------------------------------------------------

/**
 * Swap contents with another ndArray.
 */
template <typename T, unsigned N>
void ndArray<T,N>::swap( self& other )
{
    std::swap( m_numel, other.m_numel );
    for ( unsigned i = 0; i < N; ++i )
    {
        std::swap( m_size[i], other.m_size[i] );
        std::swap( m_strides[i], other.m_strides[i] );
    }
    m_data.swap( other.m_data );
}

// ------------------------------------------------------------------------

/**
 * Internal method (protected) to assign the shared pointer.
 * Dimensions are assumed to be taken care of by the public 
 * assign variants (see below); only the pointer, it's length
 * and the flag 'manage' are required here.
 *
 * 'manage' allows to specify whether or not the shared pointer 
 * should release the memory when the last refering instance is 
 * destroyed. 
 * 
 * If true, the default deleter std::default_delete will be 
 * assigned to the shared pointer. Note that this deleter releases 
 * memory allocated USING NEW ONLY; 
 *  DO NOT use malloc/calloc or other C allocation variants. 
 * 
 * If false, the deleter no_delete is given instead; this will NOT 
 * release the memory when the last refering instance is destroyed. 
 * Use only with either externally managed (eg Matlab) or static 
 * allocations.
 */
template <typename T, unsigned N>
void ndArray<T,N>::assign_shared( pointer ptr, bool manage )
{
    if (manage)
        m_data.reset( ptr );
    else
        m_data.reset( ptr, no_delete<T>() );
}

// ------------------------------------------------------------------------

/**
 * Assign from pointer and size.
 *
 * If manage == true, the internal shared pointer m_data
 * will assume ownership of the memory pointed by ptr, and
 * try to release it using delete[] when the last refering 
 * instance gets destroyed. 
 * If ptr is dynamically allocated, make sure that the 
 * allocation is performed with NEW, and NOT C variants 
 * like malloc/calloc/etc.
 *
 * If manage == false, a dummy deleter (no_delete functor) is
 * passed to the shared pointer; nothing happens to the memory 
 * pointed by ptr when the last refering instance gets destroyed.
 */
template <typename T, unsigned N>
void ndArray<T,N>::assign( pointer ptr, const unsigned *size, bool manage )
{
    if ( ptr != data() )
    {
        // Compute internal dimensions
        m_numel = 1; 
        for ( unsigned i = 0; i < N; ++i )
        {
            m_size[i] = size[i];
            m_numel  *= size[i];
            m_strides[ (i+1) % N ] = m_numel;
        }   m_strides[0] = 1;

        // Set data
        assign_shared( ptr, manage );
    }
}

// ------------------------------------------------------------------------

/**
 * Simply prints information about the dimensions of the 
 * n-dimensional array (size & number of elements).
 */
template <typename T, unsigned N>
void ndArray<T,N>::info() const
{
    if ( m_data )
    {
        printf("%u-dimensional array of size (%u", N, m_size[0]);
        for ( unsigned d = 1; d < N; ++d )
            printf(", %u", m_size[d]);
        printf(") = %u elements.\n", m_numel);
    }
    else
        printf("Empty %u-dimensional array.\n", N);
}