n 级指针的动态解除引用

Dynamic dereference of a n-level pointer

假设一个n维数组作为模板参数传递并且应该被遍历以将其保存到文件中。首先,我想找出数组包含的元素的大小。为此,我尝试取消引用指针,直到我在 [0][0][0]...[0] 处获得第一个元素。但是我在这个阶段已经失败了:

/**
 * @brief save a n-dimensional array to file
 *
 * @param arr: the n-level-pointer to the data to be saved
 * @param dimensions: pointer to array where dimensions of <arr> are stored
 * @param n: number of levels / dimensions of <arr>
 */
template <typename T>
void save_array(T arr, unsigned int* dimensions, unsigned int n){ 
        // how to put this in a loop ??       
        auto deref1 = *arr;
        auto deref2 = *deref1;
        auto deref3 = *deref2;
        // do this n times, then derefn is equivalent to arr[0]...[0], 42 should be printed
        std::cout << derefn << std::endl;
        /* further code */
}

/*
 * test call
 */
int main(){
        unsigned int dim[4] = {50, 60, 80, 50}
        uint8_t**** arr = new uint8_t***[50];
        /* further initialization of arr, omitted here */
        arr[0][0][0][0] = 42;
        save_array(arr, dim, 4);
}

当我从内存的角度考虑这个问题时,我想执行给定地址的 n 间接加载。

昨天看到一个相关的问题: Declaring dynamic Multi-Dimensional pointer

这对我也有很大帮助。一条评论指出这是不可能的,因为在编译时必须知道所有表达式的类型。在我的例子中,实际上什么都知道,save_array 的所有调用者在传递之前都会对 n 进行硬编码。所以我认为这可能只是在正确的地方定义我还不能定义的东西。

我知道我正在用 C++ 编写 C 风格的代码,并且可以选择使用 类 等来实现这一点,但我的问题是:是否有可能通过一个实现 n 级指针取消引用迭代或递归方法?谢谢!

为什么不使用像树这样具有多个子节点的数据结构。

假设您需要存储n维数组值,创建一个指向第一维的节点。假设您的第一个维度长度为 5,那么您有 5 个子节点,如果您的第二个维度大小为 10。那么对于这 5 个节点中的每一个,您都有 10 个子节点,依此类推....

类似的东西,

struct node{
    int index;
    int dimension;
    vector<node*> children;
}

遍历树会更容易,也更干净。

首先:你真的需要一个锯齿状的数组吗?你想要某种稀疏数组吗?因为否则,您能否将您的 n 维结构扁平化为一个长数组?这不仅会导致代码更简单,而且很可能也会更高效。

话虽这么说:肯定可以做到。例如,只需使用递归模板并依靠重载来剥离间接级别,直到你到达底部:

template <typename T>
void save_array(T* arr, unsigned int* dimensions)
{
    for (unsigned int i = 0U; i < *dimensions; ++i)
        std::cout << ' ' << *arr++;
    std::cout << std::endl;
}

template <typename T>
void save_array(T** arr, unsigned int* dimensions)
{
    for (unsigned int i = 0U; i < *dimensions; ++i)
        save_array(*arr, dimensions + 1);
}

您甚至不需要显式指定间接寻址的数量 n,因为该数量由指针类型隐式指定。

您也可以对 allocate/deallocate 数组执行基本相同的技巧:

template <typename T>
struct array_builder;

template <typename T>
struct array_builder<T*>
{
    T* allocate(unsigned int* dimensions) const
    {
        return new T[*dimensions];
    }
};

template <typename T>
struct array_builder<T**> : private array_builder<T*>
{
    T** allocate(unsigned int* dimensions) const
    {
        T** array = new T*[*dimensions];

        for (unsigned int i = 0U; i < *dimensions; ++i)
            array[i] = array_builder<T*>::allocate(dimensions + 1);

        return array;
    }
};

就这样,您需要部分特化,因为使用重载的方法仅在可以从参数推断出类型时才有效。由于函数不能部分特化,因此您必须将其包装在 class 这样的模板中。用法:

unsigned int dim[4] = { 50, 60, 80, 50 };
auto arr = array_builder<std::uint8_t****>{}.allocate(dim);
arr[0][0][0][0] = 42;
save_array(arr, dim);

希望我没有漏掉任何东西;公开这么多的间接访问会很快造成巨大的混乱,这就是为什么我强烈建议不要在真实代码中这样做,除非绝对不可避免。此外,到处都是 new 的这种原始用法一点也不好。理想情况下,您将使用 std::unique_ptr。或者,更好的是,按照评论中的建议嵌套 std::vectors