从C数组初始化ublas向量

6

我正在使用C++ ublas库编写Matlab扩展,并希望能够从Matlab解释器传递的C数组初始化我的ublas向量。

我该如何从C数组初始化ublas向量,而不需要(为了效率)显式地复制数据。我正在寻找以下代码行的类似内容:

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
6个回答

8
我不确定您的问题与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 ...

我知道这并不能真正回答你的问题,我只是认为你可能会发现这个概念有帮助。

11
您可以在MATLAB命令窗口中使用“format debug”命令,以查看元数据。 - Mikhail Poda
关于您的图表有一个小问题 - 您让它看起来像MATLAB创建了数据的新副本,重新分配'b'指向它,并且改变'a'指向的数据。实际上发生的是创建了数据的新副本,*a*被重新分配为指向它,然后新数据被改变。 - Chris Taylor
这与问题几乎没有任何关系。问题是关于C++的。如果您使用matlab类,它们可能会针对冗余复制进行一些编译时优化。但是,一旦您从中获取原始指针,matlab就无法阻止其他库尝试执行无用的复制。事实上,请求原始指针的操作将触发matlab对输入参数进行复制。 - Dimitry
@Dimitry 的负评是公平的,因为它没有回答问题,但我仍然会保留这个9年前的答案,即使它稍微相关...至于你最后的陈述,我应该纠正你并说,在MEX-API级别上,当您请求数字数组的原始数据(即mxGetData等)时,MATLAB不会创建副本。当然,这并不能阻止您通过将原始指针包装在std::vector中来制作副本。 - Amro
说实话,我的经验来自Octave,而Google通常会将其重定向到Matlab答案。通过提供原始内存指针,Matlab / Octave数组容器放弃了它们对内存访问的任何控制,并为了保证使用该指针不会引起副作用,它们必须确保内存不被其他对象共享。除非您在所有内容中都加入“const”修饰符,否则Octave会在指针请求时执行此操作。Matlab mex编译器可能更为复杂并跟踪指针使用情况,也可能不这样做。 - Dimitry
@Dimitry 不,MEX-API 简单地将指针交给您,由您自己来避免错误。 :) - Amro

6

std::vectorublas::vector都是容器。容器的主要作用是管理所包含对象的存储和生命周期。因此,当您初始化它们时,它们必须将值复制到自己拥有的存储中。

C数组是固定大小和位置的内存区域,因此你只能通过复制来将其值放入容器中。

您可以将C数组用作许多算法函数的输入,因此也许可以这样做以避免初始复制?


2
除了在理论上你可以创建一个ublas::vector的子类来实现这一点之外。你的子类可以表现得像一个不可调整大小的const ublas::vector,或者你必须覆盖所有涉及调整容器大小的方法,以确保它们不会释放不属于它的内存。 只有一个完全的受虐狂才会尝试这样做。 - Die in Sente

4
您可以轻松地从C数组初始化std :: vector:
vector<int> v(pv, pv+10);

谢谢您的回答,但这样会复制数据。我希望vpv指向同一块数据。 - D R
1
你不能这样做。std::vector总是拥有它的内存。不过你可以编写自己的向量类... - shoosh

4
在uBLAS storage.hpp中有两个未记录的类。您可以使用其中一个更改ublas :: vector中的默认存储类(unbounded_array)。
  • 第一个类,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

3
使用浅层数组适配器的通常建议对我来说有点讽刺 - 为了能够简单地通过指针访问数组,您应该将其放入带有所有引用计数的共享数组中(这些计数归零,因为您不拥有该数组),还有一个数据别名的噩梦。 实际上,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的resize方法动态地将向量附加和分离到外部存储器(保留或舍弃数据)。在重新调整大小时,它会自动从存储中分离出来并成为常规向量。从容器中赋值直接进入存储器中,但是从表达式中赋值则通过一个临时变量进行,并且向量从存储器中分离出来,请使用noalias()来防止这种情况发生。构造函数中存在少量开销,因为data_是私有成员,我们必须使用new T[0]进行默认初始化,然后重新分配给外部数组。你可以将其更改为protected,并直接在构造函数中分配给存储器。

2
这里有几个语法方便的赋值函数(承认不是初始化):
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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接