问题
We know that Resource Acquisition is Initialization (RAII), I looked for the syntax to initialize an array of objects that have parameters (with no default params), managed by unique_ptr
but I did not find any example, there is one in Cppreference constructing int
int size = 10;
std::unique_ptr<int[]> fact(new int[size]);
How could I write like this:
class Widget
{
Widget(int x, in y):x_(x),y_(y)
{}
int x_,y_;
};
std::unique_ptr<Widget[]> fact(new Widget[size]);
回答1:
Following the last answer in the recommended link
How can I make new[] default-initialize the array of primitive types?,
I came up with the following small example:
#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;
}
Output:
label 1
label 2
label 3
Live Demo on coliru
The trick is to use an initializer list.
I was a bit unsure whether this involves copy construction (which is often forbidden in widget class libaries). To be sure, I wrote Widget(const Widget&) = delete;
.
I have to admit that this works with C++17 but not before.
I fiddled a bit with the first example.
I tried also
new Widget[n]{
{ "label 1" },
{ "label 2" },
{ "label 3" }
});
with success until I realized that I forgot to make the constructor explicit
in first example. (Usually, a widget set wouldn't allow this – to prevent accidental conversion.) After fixing this, it didn't compile anymore.
Introducing, a move constructor it compiles even with 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;
}
Output: like above
Live Demo on coliru
回答2:
You can use placement new and custom deleter:
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;
}
As expected we have two lines output:
0123456789
0123456789
Observe, that because the deleter is stateful here it is impossible to use default constructor of std::unique_ptr<Widget[], WidgetDeleter>
[unique.ptr.single.ctor#8]
来源:https://stackoverflow.com/questions/52216835/how-to-initialize-elements-of-an-array-managed-by-a-unique-ptr