4

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]);
Yola
  • 18,496
  • 11
  • 65
  • 106
abdulrhmanOmran
  • 111
  • 1
  • 7
  • 6
    Have a look at the answers to [this similar question](https://stackoverflow.com/q/2468203/2501078). – nafmo Sep 07 '18 at 06:53
  • 4
    Use `std::vector`. – Daniel Langr Sep 07 '18 at 06:53
  • 2
    Possible duplicate of [How can I make \`new\[\]\` default-initialize the array of primitive types?](https://stackoverflow.com/questions/2468203/how-can-i-make-new-default-initialize-the-array-of-primitive-types) – Daniel Langr Sep 07 '18 at 06:59
  • 1
    thank you @DanielLangr , it is a part of design that need this from – abdulrhmanOmran Sep 07 '18 at 07:01
  • 2
    there are no duplication I already provided an example std::unique_ptr fact(new int[size]); I need to initialize object with params – abdulrhmanOmran Sep 07 '18 at 07:02
  • 1
    @abdulrhmanOmran I am afraid you cannot do this. Your example and your "need" cannot change that. – Daniel Langr Sep 07 '18 at 07:15
  • 1
    why we can do that with calss without parameters. std::unique_ptr fact1(new Widget[size]); – abdulrhmanOmran Sep 07 '18 at 07:34
  • @DanielLangr placement new could help. Yes, this is ugly, as can be seen in the answer below, but sometimes one needs dynamic array, not resizable vector. AFAK, C++ doesn't provide it yet. – Yola Apr 27 '19 at 18:58
  • @Yola What is the downside of `std::vector` when compared to a dynamic array? Basically few additional bytes in memory plus a requirement for the value type to be move-insertable. These only rarely matter in practice. – Daniel Langr Apr 29 '19 at 06:50
  • @DanielLangr Sorry for the delay, was away. The difference is in the semantics. Sometimes I want the reader to see that number of elements is fixed after creation. The [dynarray](http://www.open-std.org/JTC1/sc22/WG21/docs/papers/2013/n3662.html) proposal tried to fill the gap, but wasn't adopted. – Yola May 06 '19 at 07:22

2 Answers2

4

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

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
1

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]

Yola
  • 18,496
  • 11
  • 65
  • 106
  • What if the constructor of `Widget` may throw an exception? Your code will get quite complicated to be exception-safe. And, you fill finally just implement manually what `std::vector` provides for you. – Daniel Langr Apr 29 '19 at 06:47
  • @DanielLangr I agree that such code should be carefully written and reside in a library. – Yola May 06 '19 at 07:23