Инициализация вектора ublas из массива C

Я пишу расширение Matlab, используя библиотеку ublas C++, и я хотел бы иметь возможность инициализировать свои векторы ublas из массивов C, переданных интерпретатором Matlab. Как я могу инициализировать вектор ublas из массива C без (ради эффективности) явного копирования данных. Я ищу что-то в следующих строках кода:

using namespace boost::numeric::ublas;

int pv[10] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };
vector<int> v (pv);

В общем, можно ли инициализировать C++ std::vector из массива? Что-то вроде этого:

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int pv[4] = { 4, 4, 4, 4};
    vector<int> v (pv, pv+4);

    pv[0] = 0;
    cout << "v[0]=" << v[0] << " " << "pv[0]=" << pv[0] << endl;

    return 0;
}

но где инициализация не будет копировать данные. В этом случае выход

v[0]=4 pv[0]=0

но я хочу, чтобы результат был таким же, когда обновление массива C изменяет данные, на которые указывает вектор C++

v[0]=0 pv[0]=0

person D R    schedule 14.11.2009    source источник


Ответы (6)


И std::vector, и ublas::vector являются контейнерами. Весь смысл контейнеров заключается в управлении хранением и временем жизни содержащихся в них объектов. Вот почему, когда вы их инициализируете, они должны копировать значения в хранилище, которым они владеют.

Массивы C представляют собой области памяти с фиксированным размером и расположением, поэтому по своей природе вы можете получить их значения в контейнер только путем копирования.

Вы можете использовать массивы C в качестве входных данных для многих функций алгоритма, поэтому, возможно, вы можете сделать это, чтобы избежать первоначальной копии?

person CB Bailey    schedule 14.11.2009
comment
За исключением того, что теоретически вы могли бы создать подкласс ublas::vector, который делал бы это. Ваш подкласс может вести себя как const ublas::vector, размер которого никогда не может быть изменен, или вам придется переопределить все методы, связанные с изменением размера контейнера, чтобы гарантировать, что память, которая ему не принадлежит, не освобождается. . Только законченный мазохист будет пытаться это сделать. - person Die in Sente; 16.11.2009

Я не уверен, как ваш вопрос относится к MATLAB/MEX, но примечание: вам может быть интересно узнать, что MATLAB реализует стратегию копирования при записи.

Это означает, что когда вы, например, копируете массив, на самом деле копируются только некоторые заголовки, а сами данные распределяются между двумя массивами. И как только один из них изменен, фактически создается копия данных.

Ниже приведено моделирование того, что может происходить под капотом (позаимствовано из этого старого сообщение):

-----------------------------------------
>> a = [35.7 100.2 1.2e7];

 mxArray a
    pdata -----> 35.7 100.2 1.2e7
  crosslink=0

-----------------------------------------
>> b = a;

 mxArray a
    pdata -----> 35.7 100.2 1.2e7
  crosslink     / \
    |  / \       |
    |   |        |
    |   |        |
   \ /  |        |
   crosslink     |
 mxArray b       |
    pdata --------

-----------------------------------------
>> a(1) = 1;

mxArray a
    pdata -----> (1) 100.2 1.2e7
  crosslink=0


   crosslink=0
 mxArray b
    pdata ------> 35.7 100.2 1.2e7 ...

Я знаю, что на самом деле это не отвечает на ваш вопрос, я просто подумал, что вы можете найти эту концепцию полезной.

person Amro    schedule 14.11.2009
comment
Вы можете увидеть эти метаданные в формате настройки окна команд MATLAB с помощью format debug - person Mikhail; 07.08.2010
comment
Незначительный момент в вашей диаграмме - вы делаете так, как будто MATLAB создает новую копию данных, переназначает b, чтобы указать на нее, и изменяет данные, на которые указывает a. На самом деле происходит то, что создается новая копия данных и a переназначается, чтобы указать на нее, а затем новые данные изменяются. - person Chris Taylor; 03.03.2015
comment
Это практически не имеет отношения к вопросу. Вопрос про С++. Если вы используете классы Matlab, они могут иметь некоторую оптимизацию времени компиляции против избыточного копирования. Как только вы берете из него необработанный указатель, Matlab не может предотвратить попытки других библиотек сделать бесполезные копии. Фактически, сама операция запроса необработанного указателя заставит Matlab скопировать входные параметры. - person Dimitry; 25.08.2018
comment
@Dimitry downvote достаточно справедлив, поскольку он не отвечает на вопрос, но я все же сохраню этот 9-летний ответ, даже если он немного актуален ... Что касается вашего последнего утверждения, я должен исправить вас и сказать это на MEX-API уровень, MATLAB не создает копию, когда вы запрашиваете необработанные данные числового массива (т.е. mxGetData и т.п.). Конечно, это не мешает вам делать копии, заключая необработанный указатель в std::vector. - person Amro; 26.08.2018
comment
Ну, если честно, мой опыт исходит от Octave, который Google все равно перенаправляет на ответы Matlab. Предоставляя необработанный указатель памяти, контейнеры массива Matlab\Octave отказываются от любого контроля над доступом к памяти, который у них был, и чтобы гарантировать, что побочные эффекты не будут вызваны использованием этого указателя, они должны убедиться, что память не используется другими объектами. . Octave делает это при запросе указателя, если вы не приправляете все модификаторами const. Компилятор Matlab mex может быть более сложным и отслеживать использование указателя, а может и нет. - person Dimitry; 26.08.2018
comment
@Dimitry Нет, MEX-API просто передает вам указатель, и вам решать, не выстрелить ли себе в ногу :) - person Amro; 26.08.2018

Вы можете легко инициализировать std::vector из массива C:

vector<int> v(pv, pv+10);
person ebo    schedule 14.11.2009
comment
Спасибо за ваш ответ, но это скопирует данные. Я хочу, чтобы v и pv указывали на один и тот же блок данных. - person D R; 15.11.2009
comment
Вы не можете иметь это. std::vector всегда владеет своей памятью. Вы можете написать свой собственный векторный класс... - person shoosh; 15.11.2009

В uBLAS storage.hpp есть два недокументированных класса. Вы можете изменить класс хранения по умолчанию (unbounded_array) в ublas::vector с помощью одного из них.

  • Первый класс, array_adaptor, делает копию ваших данных, когда ublas::vector вызывает конструктор копирования, вообще не очень полезный класс. Я бы предпочел просто подходящий конструктор, чтобы сделать это в классах unbounded_array илиbounded_array.
  • Второй, shallow_array_adaptor, содержит только ссылку на ваши данные, поэтому вы можете использовать вектор для прямого изменения массива C. К сожалению, у него есть некоторые ошибки, когда вы присваиваете выражение, оно теряет исходный указатель данных. Но вы можете создать производный класс, который решит эту проблему.

Вот патч и пример:

// BOOST_UBLAS_SHALLOW_ARRAY_ADAPTOR must be defined before include vector.hpp
#define BOOST_UBLAS_SHALLOW_ARRAY_ADAPTOR

#include <boost/numeric/ublas/vector.hpp>
#include <algorithm>
#include <iostream>

// Derived class that fix base class bug. Same name, different namespace.    
template<typename T>
class shallow_array_adaptor
: public boost::numeric::ublas::shallow_array_adaptor<T>
{
public:
   typedef boost::numeric::ublas::shallow_array_adaptor<T> base_type;
   typedef typename base_type::size_type                   size_type;
   typedef typename base_type::pointer                     pointer;

   shallow_array_adaptor(size_type n) : base_type(n) {}
   shallow_array_adaptor(size_type n, pointer data) : base_type(n,data) {}
   shallow_array_adaptor(const shallow_array_adaptor& c) : base_type(c) {}

   // This function must swap the values ​​of the items, not the data pointers.
   void swap(shallow_array_adaptor& a) {
      if (base_type::begin() != a.begin())
         std::swap_ranges(base_type::begin(), base_type::end(), a.begin());
   }
};

void test() {
    using namespace boost::numeric;
    typedef ublas::vector<double,shallow_array_adaptor<double> > vector_adaptor;

    struct point {
        double x;
        double y;
        double z;
    };

    point p = { 1, 2, 3 };
    vector_adaptor v(shallow_array_adaptor<double>(3, &p.x));

    std::cout << p.x << ' ' << p.y << ' ' << p.z << std::endl;
    v += v*2.0;
    std::cout << p.x << ' ' << p.y << ' ' << p.z << std::endl;
}

Вывод:

1 2 3
3 6 9
person Guillermo Ruiz    schedule 28.02.2012

Вот пара функций для синтаксически удобного присваивания (правда, не инициализации):

vector<int> v;
setVector(v, 3, 
          1, 2, 3);

matrix<int> m;
setMatrix(m, 3, 4,
            1,   2,   3,   4,
           11,  22,  33,  44,
          111, 222, 333, 444);

Функции:

/**
 * Resize a ublas vector and set its elements
 */
template <class T> void setVector(vector<T> &v, int n, ...)
{
    va_list ap;
    va_start(ap, n);
    v.resize(n);
    for (int i = 0; i < n; i++) {
        v[i] = va_arg(ap, T);
    }
    va_end(ap);
}

/**
 * Resize a ublas matrix and set its elements
 */
template <class T> void setMatrix(matrix<T> &m, int rows, int cols ...)
{
    va_list ap;
    va_start(ap, cols);
    m.resize(rows, cols);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            m(i, j) = va_arg(ap, T);
        }
    }
    va_end(ap);
}
person Nasorenga    schedule 06.01.2012

Обычное предложение использовать неглубокий адаптер массива кажется мне саркастическим - чтобы иметь возможность просто получить доступ к массиву через указатель, вы должны поместить его в shared_array со всеми подсчетами ссылок (это ни к чему не приводит, поскольку вы не владеете массивом) и более того, с кошмаром алиасинга данных. Собственно, в uBLAS есть полноценная реализация хранилища (array_adaptor), которая позволяет использовать векторы с внешними массивами c. Единственная загвоздка - векторный конструктор, который делает копию. Почему эта приятная функция не используется в библиотеке, мне совершенно непонятно, но в любом случае мы можем использовать небольшое расширение (на самом деле это 2 строки кода, окруженные обычным наворотом C++)

template<class T>
class extarray_vector :
    public vector<T, array_adaptor<T> >
{
    typedef vector<T, array_adaptor<T> > vector_type;
public:
    BOOST_UBLAS_INLINE
    extarray_vector(size_type size, pointer p)
    { data().resize(size, p); }

    template <size_type N>
    BOOST_UBLAS_INLINE
    extarray_vector(T (&a)[N])
    { data().resize(N, a); }

    template<class V>
    BOOST_UBLAS_INLINE
    extarray_vector& operator = (const vector<T, V>& v)
    {
        vector_type::operator = (v);
        return *this;
    }

    template<class VC>
    BOOST_UBLAS_INLINE
    extarray_vector& operator = (const vector_container<VC>& v)
    {
        vector_type::operator = (v);
        return *this;
    }

    template<class VE>
    BOOST_UBLAS_INLINE
    extarray_vector& operator = (const vector_expression<VE>& ae)
    {
        vector_type::operator = (ae);
        return *this;
    }
};

вы можете использовать его следующим образом:

int i[] = {1, 4, 9, 16, 25, 36, 49};
extarray_vector<int> iv(i);
BOOST_ASSERT_MSG(i == &iv[0], "Vector should attach to external array\n");
iv[3] = 100;
BOOST_ASSERT(i[3] == 100);
iv.resize(iv.size() + 1, true);
BOOST_ASSERT_MSG(i != &iv[0], "And detach from the array on resize\n");
iv[3] = 200;
BOOST_ASSERT(i[3] == 100);
iv.data().resize(7, i, 0);
BOOST_ASSERT_MSG(i == &iv[0], "And attach back to the array\n");
BOOST_ASSERT(i[3] == 200);

Вы можете динамически прикреплять и отсоединять вектор от внешнего хранилища с помощью метода изменения размера array_adaptor (сохранение или удаление данных). При изменении размера он автоматически отделяется от хранилища и становится обычным вектором. Присвоение из контейнеров идет прямо в хранилище, но присваивание из выражения выполняется через временное, а вектор отсоединяется от хранилища, используйте noalias(), чтобы предотвратить это. В конструкторе есть небольшие накладные расходы, поскольку data_ является закрытым членом, и мы должны по умолчанию инициализировать его новым T[0], а затем переназначить внешнему массиву. Вы можете изменить его на защищенный и назначить хранилище прямо в конструкторе.

person panda-34    schedule 23.06.2012