从旧的 C 风格指针转移到 C++ 智能指针,代码几乎没有变化?

Moving from old C-style pointer to C++ smart pointers with little changes in the code?

我有一个函数,其中二进制 'tree' 的节点填充有基于输入向量递归计算的值,它表示叶子上的值。该函数的旧 C++ 实现如下

using namespace std;

double f(const size_t h, vector<double>& input) {
    double** x;
    x = new double*[h+1];
    x[0] = input.data();

    for (size_t l = 1; l <= h; l++)
            x[l] = new double[1<<(h-l)];
    // do the computations on the tree where x[l][n] is the value 
    // on the node n at level l.


    result = x[l][0];

    for (size_t l = 1; l <= h; l++)
            delete[] x[l];

    delete[] x;

    return result;
}

现在我正在尝试使用 C++11/C++14 中的智能指针编写代码的 'modern' 实现。我尝试使用数组的 std::unique_ptr 特化来定义 x,这样我就不必更改 'computation' 过程。这种方法的明显问题是“input”的内容将在函数结束时被删除(因为取得数据所有权的唯一指针将在函数结束时被销毁)。

一个简单(也许安全)的解决方案是在 x 中为整棵树(包括叶子)分配内存,并 复制 的值在函数的开头从 inputx[0] 离开(在这种情况下,我什至可以使用嵌套的 std::vectors 而不是专门用于数组的 std::unique_ptrs 作为 x).但我更愿意避免复制的成本。

或者可以更改计算过程以直接从 input 而不是从 x 读取叶子的值,这需要更改太多的小代码段。

还有其他方法吗?

这看起来确实像是 std::vector<std::vector<double>> 的工作,而不是 std::unique_ptr,但具有额外的复杂性,您在概念上希望向量仅拥有 部分 [=29] =] 的内容,而第一个元素是对输入向量的非拥有引用(而不是副本)。

这不是直接可行的,但您可以添加一个额外的间接层来实现所需的效果。如果我正确理解你的问题,你希望 x 支持 operator[],其中参数 0 指的是 input,而参数 > 0 指的是 [=] 拥有的数据13=]本身。

我会为此编写一个根据 std::vector 实现的简单容器。这是一个玩具示例;我将容器命名为 SpecialVector:

#include <vector>

double f(const std::size_t h, std::vector<double>& input) {

    struct SpecialVector {
        SpecialVector(std::vector<double>& input) :
            owned(),
            input(input)
        {}

        std::vector<std::vector<double>> owned;
        std::vector<double>& input;

        std::vector<double>& operator[](int index) {
            if (index == 0) {
                return input;
            } else {
                return owned[index - 1];
            }
        }

        void add(int size) {
            owned.emplace_back(size);
        }
    };

    SpecialVector x(input);

    for (std::size_t l = 1; l <= h; l++)
            x.add(1<<(h-l));
    // do the computations on the tree where x[l][n] is the value 
    // on the node n at level l.

    auto result = x[1][0];

    return result;
}

int main() {
    std::vector<double> input { 1.0, 2.0, 3.0 };
    f(10, input);
}

这种方法允许遗留代码的其余部分继续使用 [] 与以前完全一样。

C++11/14 并没有真正引入任何在使用 modern std::vector 管理动态数组内存之前无法实现的内容。

The obvious problem with [std::unique_ptr] is that the contents of `input' will be deleted at the end of the function

确实如此。您不能 "steal" 输入向量的缓冲区(除非通过交换或移动进入另一个向量)。这会导致未定义的行为。

Alternatively one can change the computational procedures to read the values of the leaves directly from input not from x which requires changing too many small pieces of the code.

这个备选方案很有意义。不清楚 为什么 输入向量必须由 x[0] 指向。循环从 1 开始,所以它似乎没有被它们使用。如果只是直接引用它,那么使用输入参数本身会更有意义。使用显示的代码,我希望这会大大简化您的功能。

Also the fact that the input is not taken as const std::vector& bothers me.

这是不指向可修改 x[0] 中的输入向量的另一个原因。但是,可以使用 const_cast 解决该限制。 const_cast 就是针对这种情况。

让我们从今往后假设输入是本地数组数组的一部分是有意义的。

One simple (and perhaps safe) solution would be to allocate the memory for the whole tree (including the leaves) in x ... I can even used nested std::vectors ... But I prefer to avoid the cost of copying.

如果您使用向量的向量,则不一定需要复制。您可以将输入向量交换或移动到 x[0]。处理完成后,您可以根据需要通过交换或返回来恢复输入。 None 如果您按照建议将输入分开,则这是必需的。


我建议另一种方法。以下建议主要是性能优化,因为它将分配的数量减少到 2。作为奖励,它恰好也很容易满足您从本地数组数组指向输入向量的愿望。这个想法是将所有树分配到一个平面向量中,并为裸指针分配另一个向量到内容向量中。

这里是一个使用输入向量作为x[0]的例子,但是如果你选择直接使用输入的话,很容易改变。

double f(const size_t h, const std::vector<double>& input) {
    std::vector<double*> x(h + 1);
    x[0] = const_cast<double*>(input.data()); // take extra care not to modify x[0]

    // (1 << 0) + (1 << 1) + ... + (1 << (h-1)) == (1 << h) - 1
    std::vector<double> tree((1 << h) - 1);
    for (std::size_t index = 0, l = 1; l <= h; l++) {
        x[l] = &tree[index];
        index += (1 << (h - l));
    }

    // do the computations on the tree where x[l][n] is the value 
    // on the node n at level l.

    return x[l][0];
}

写一个classRow,其中包含一个所有权控制销毁行为的标志并实现operator[],然后创建一个vector行。

如上所述,如果 input 是常量,你会遇到问题,因为你不能在编译器级别明确地强制执行它,你必须小心不要写在你不能写的地方,但这并不比那时更糟你现在拥有的。

我没有尝试编译它,但是你的新 Row class 可能看起来有点像这样。

class Row
{
   double *p;
   bool f;
public:
   Row() :p(0), f(false) {}
   void init(size_t n) { p = new double[n]; f=true; }
   void init(double *d) { p=d;, f=false;}
   double operator[](size_t i) { return p[i]; }
   ~Row() { if (flag) delete[] p; }
};