Как инициализировать элементы массива под управлением unique_ptr? - PullRequest
0 голосов
/ 07 сентября 2018

Мы знаем, что Resource Acquisition - Initialization (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]);

Ответы [ 2 ]

0 голосов
/ 27 апреля 2019

Вы можете использовать размещение нового и пользовательского удалителя:

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]

0 голосов
/ 07 сентября 2018

После последнего ответа в рекомендованной ссылке
Как мне сделать 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

...