9

When trying to create a shared pointer to an object I come across this error

#include <iostream>
#include <vector>

class Foo {
public:
    std::shared_ptr<Foo> getFoo(int i) {
        auto foo = std::make_shared<Foo>(i);
        //auto foo = std::shared_ptr<Foo>(new Foo(i)); "works"
        return foo;
    }
protected:
    Foo(int i) {std::cout << "foo" << std::endl; }
};

int main(int argc, const char * argv[]) {

}

I get the buildtime error

 Static_assert failed due to requirement 'is_constructible<Foo, int &>::value' "Can't construct object in make_shared"

I can avoid this by making the constructor private, But I would like to know why make_shared fails and and shared_ptr(new) works?

2 Answers2

7
//auto foo = std::shared_ptr<Foo>(new Foo(i)); "works"

Here you access Foo's constructor from inside Foo and then pass this pointer to std::shared_ptr, which is fine.

auto foo = std::make_shared<Foo>(i);

Here std::make_shared tries to access Foo's constructor which is protected and thus not allowed.

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • This actually makes sense now. If I defined a function outside of foo, which attempted to construct a foo object, and tried to call that function within getFoo I would also get an error. For the same reason. Thanks. –  May 30 '19 at 12:35
2

@SombreroChicken provided a good answer why this is the case. I want to provide a possible solution

#include <iostream>
#include <memory>

class Foo {
private:
    struct Token{};
public:
    std::shared_ptr<Foo> getFoo(int i) {
        auto foo = std::make_shared<Foo>(i, Token{});
        //auto foo = std::shared_ptr<Foo>(new Foo(i)); "works"
        return foo;
    }
    Foo(int i, Token) : Foo(i) {}

protected:
    Foo(int i) {std::cout << "foo" << std::endl; }
};

int main(int argc, const char * argv[]) {

}

it works by making a public constructor that requires something only Foo can make, so anyone who received the 'token' can call the constructor

NOTE: passing such tag parameters may be better done as the first parameter, because

  1. to be consistent with standard library

  2. certain compilers (notably msvc) have special handling of it that improves compile times

Sopel
  • 1,179
  • 1
  • 10
  • 15
  • Thats an interesting approach. Note that constructors are special because from any other method one could apply a little template magic to create a `Token` from outside `Foo` even if it is declared privately, though for constructors a more hacky approach is needed (see [here](https://stackoverflow.com/questions/41623899/c-obtaining-the-type-of-a-constructor)) – 463035818_is_not_an_ai Aug 01 '19 at 18:58