如何初始化由unique_ptr管理的数组元素?

4
我们知道资源获取即初始化(RAII),我正在寻找初始化具有参数的对象数组的语法(没有默认参数),这些对象由unique_ptr管理,但我没有找到任何示例。在Cppreference中有一个构造int的示例。
int size = 10; 
std::unique_ptr<int[]> fact(new int[size]);

我该如何编写像这样的代码:


class Widget
{
 Widget(int x, in y):x_(x),y_(y)
 {}
 int x_,y_;
};

 std::unique_ptr<Widget[]> fact(new Widget[size]);

6
请查看这个类似问题的答案:这里 - nafmo
4
请使用 std::vector - Daniel Langr
2
可能是如何使 `new[]` 默认初始化原始类型数组?的重复问题。 - Daniel Langr
1
谢谢@DanielLangr,这是需要此设计的一部分。 - abdulrhmanOmran
2
我需要使用参数初始化对象,例如:std::unique_ptr<int[]> fact(new int[size]);。 - abdulrhmanOmran
显示剩余5条评论
2个回答

4

在推荐链接的最后一个答案中(如何使new[]默认初始化原始类型数组?), 我想出了以下小例子:

#include <iostream>
#include <memory>
#include <string>

class Widget {
  private:
    std::string _name;
  public:
    Widget(const char *name): _name(name) { }
    Widget(const Widget&) = delete;
    const std::string& name() const { return _name; }
};

int main()
{
  const int n = 3;
  std::unique_ptr<Widget[]> ptrLabels(
    new Widget[n]{
      Widget("label 1"),
      Widget("label 2"),
      Widget("label 3")
    });
  for (int i = 0; i < n; ++i) {
    std::cout << ptrLabels[i].name() << '\n';
  }
  return 0;
}

输出:

label 1
label 2
label 3

在coliru上的实时演示

关键是使用初始化器列表。

我有些不确定这是否涉及复制构造(在部分窗口小部件类库中通常被禁止使用)。为了确保,我写了 Widget(const Widget&) = delete;

我必须承认,这适用于C++17但不适用于之前的版本。


我稍微调整了第一个例子。

我也尝试过

new Widget[n]{
  { "label 1" },
  { "label 2" },
  { "label 3" }
});

在第一个例子中,我以为一切都很顺利,直到意识到我忘了将构造函数设置为explicit(通常情况下,窗口部件集不会允许这样的操作,以防止意外转换)。修复后,无法编译。

引入移动构造函数后,在C++11的环境下即可编译:

#include <iostream>
#include <memory>
#include <string>

class Widget {
  private:
    std::string _name;
  public:
    explicit Widget(const char *name): _name(name) { }
    Widget(const Widget&) = delete;
    Widget(const Widget &&widget): _name(std::move(widget._name)) { }
    const std::string& name() const { return _name; }
};

int main()
{
  const int n = 3;
  std::unique_ptr<Widget[]> ptrLabels(
    new Widget[n]{
      Widget("label 1"),
      Widget("label 2"),
      Widget("label 3")
    });
  for (int i = 0; i < n; ++i) {
    std::cout << ptrLabels[i].name() << '\n';
  }
  return 0;
}

输出:与上面的相同

在coliru上的实时演示


1
你可以使用定位 new 和自定义删除器:

class Widget {
public:
    int i;
    Widget(int i) : i(i) {}
    ~Widget() { std::cout << i; }
};

class WidgetDeleter {
    int _size;
public:
    WidgetDeleter(int size) : _size(size) {}
    void operator()(Widget* w) { 
        for (int i = 0; i < _size; ++i) w[i].~Widget();
    }
}; 

void main() {
    const int widgetsCount = 10;
    auto widgets = std::unique_ptr<Widget[], WidgetDeleter>(
        (Widget*)(new byte[widgetsCount * sizeof(Widget)]), WidgetDeleter(widgetsCount));
    for (int i = 0; i < widgetsCount; ++i) new (widgets.get() + i)Widget(i);
    for (int i = 0; i < widgetsCount; ++i) std::cout << widgets[i].i;
    std::cout << std::endl;
}

正如预期的那样,我们有两行输出:

0123456789
0123456789

请注意,由于删除器在此处是有状态的,因此无法使用 std::unique_ptr<Widget[], WidgetDeleter> 的默认构造函数[unique.ptr.single.ctor#8]


如果 Widget 的构造函数可能会抛出异常,那么你的代码将变得非常复杂以确保异常安全。此外,你还需要手动实现 std::vector 已经为你提供的功能。 - Daniel Langr
@DanielLangr 我同意这样的代码应该小心编写并存放在库中。 - Yola

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