How to return a class instance on the heap, when the relevant ctor is private?

178 Views Asked by At

Suppose I have this struct

struct MyStruct {

  static MyStruct Create(int x) {
    return { x*2, x>3 };
  }

  MyStruct(const MyStruct& c) = delete;  // no copy c'tor

private:
  MyStruct(int a_, bool b_) : a(a_), b(b_) {}  // private c'tor -- can't use new

  const int a;
  const bool b;
};

Edit: I deleted the copy constructor. This is simplified example of some classes I have in my codebase where they don't have copy c'tors.

I can get an instance on the stack like so:

int main() {
  auto foo = MyStruct::Create(2);
  return 0;
}

But suppose I need a pointer instead (or unique_ptr is fine), and I can't change the implementation of MyStruct, how can I do that?

5

There are 5 best solutions below

9
On BEST ANSWER

You could wrap MyStruct in another class, which has a MyStruct member. Here's a minimal version of that:

class Wrapper {
public:
    MyStruct ms;
    Wrapper(int x) : ms(MyStruct::Create(x)) { }
};

which you can use like so:

int main() {
  MyStruct::Create(2);
  std::make_unique<Wrapper>(2);
}

This code will not trigger any copies nor moves - because of copy elision (see: What are copy elision and return value optimization?).

You can then add any other constructors and methods you like to such a wrapper, possibly forwarding some of the method calls to the ms member. Some might choose to make ms protected or private.

1
On

One more way to do it:

  struct ChildStruct : public MyStruct {                                                                                  
      ChildStruct(int x) : MyStruct(MyStruct::Create(x))                             
      {}                                                                             
                                                                                     
  };                                                                                 
                                                                                     
  int main() {                                                                       
    MyStruct *foo1 = new ChildStruct(2);                                             
    return 0;                                                                        
  }  
2
On

C style solution. I am not sure that this is not UB, but for simple struct with 2 integer fields it should work.

int main() {                                                                    
    auto foo = MyStruct::Create(2);                                               
                                                                                  
    MyStruct *p = (MyStruct*)malloc(sizeof(MyStruct));                            
    memcpy(p, &foo, sizeof(MyStruct));                                            
    //...
    free(p);
    return 0;                                                                     
}
0
On

A comment rather than an answer, to avoid confusion for future readers.

I can get an instance on the stack like so:

int main() {
  auto foo = MyStruct::Create(2);
  return 0;
}

Note that this is only true as of C++17 and guaranteed copy elision, whereas the program is ill-formed is C++14, as even if the copy may be elided, the initialization of foo is copy-initialization from a temporary (in C++17: the temporary is never materialized).

3
On

Is this what you're looking for?

auto baz  = std::make_unique<MyStruct>( MyStruct::Create(2) );  // unique pointer