0

I hope to prevent the users from creating new instance through the constructor, so I mark the constructor as a private method.

What's more, I need to provide a method to return an object which is used to automatically manage the life of the instance.The function in the code snippet below is getFoo().

Here is the code snippet which I wrote at first:

#include <iostream>
#include <memory>
 
class Foo : public std::enable_shared_from_this<Foo> {
private:     //the user should not construct an instance through the constructor below.                    
    Foo(int num):num_(num) { std::cout << "Foo::Foo\n"; }
public:
    ~Foo() { std::cout << "Foo::~Foo\n"; } 
    std::shared_ptr<Foo> getFoo() { return shared_from_this(9); }
private:
    int num_;
};

Since the constructor is marked as private, so there is no way to create an instance, which causes getFoo could never be called. So I updated the code snippet above.

Here is the code snippet:

#include <iostream>
#include <memory>
 
class Foo : public std::enable_shared_from_this<Foo> {
private:     //the user should not construct an instance through the constructor below.                    
    Foo(int num):num_(num) { std::cout << "Foo::Foo\n"; }
public:
    ~Foo() { std::cout << "Foo::~Foo\n"; } 
static std::shared_ptr<Foo> Create() { return std::make_shared<Foo>(5); }
private:
    int num_;
};
 
int main() {
    auto pf = Foo::Create(); 
}

But it does not compile, indeed. Here is what the compiler complains:

In  file included from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/alloc_traits.h:33,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/ext/alloc_traits.h:34,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/basic_string.h:40,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/string:53,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/locale_classes.h:40,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/ios_base.h:41,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/ios:42,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/ostream:38,
                 from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/iostream:39,
                 from <source>:1:
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/stl_construct.h: In instantiation of 'void std::_Construct(_Tp*, _Args&& ...) [with _Tp = Foo; _Args = {int}]':
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/alloc_traits.h:635:19:   required from 'static void std::allocator_traits<std::allocator<void> >::construct(allocator_type&, _Up*, _Args&& ...) [with _Up = Foo; _Args = {int}; allocator_type = std::allocator<void>]'
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/shared_ptr_base.h:604:39:   required from 'std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc, _Args&& ...) [with _Args = {int}; _Tp = Foo; _Alloc = std::allocator<void>; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/shared_ptr_base.h:971:16:   required from 'std::__shared_count<_Lp>::__shared_count(_Tp*&, std::_Sp_alloc_shared_tag<_Alloc>, _Args&& ...) [with _Tp = Foo; _Alloc = std::allocator<void>; _Args = {int}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/shared_ptr_base.h:1712:14:   required from 'std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = std::allocator<void>; _Args = {int}; _Tp = Foo; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/shared_ptr.h:464:59:   required from 'std::shared_ptr<_Tp>::shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = std::allocator<void>; _Args = {int}; _Tp = Foo]'
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/shared_ptr.h:1009:14:   required from 'std::shared_ptr<typename std::enable_if<(! std::is_array< <template-parameter-1-1> >::value), _Tp>::type> std::make_shared(_Args&& ...) [with _Tp = Foo; _Args = {int}; typename enable_if<(! is_array< <template-parameter-1-1> >::value), _Tp>::type = Foo]'
<source>:9:68:   required from here
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/stl_construct.h:119:7: error: 'Foo::Foo(int)' is private within this context
  119 |       ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:6:5: note: declared private here
    6 |     Foo(int num):num_(num) { std::cout << "Foo::Foo\n"; }
      |     ^~~

It really surprises that Create() could not call the private constructor. I think any member function should have the allowance to invoke another private member variable. If I miss something, please let me know.

Note: I only could use C++11.

Five minutes later, I realise where am I wrong. Create() is not a member function, it's a static function. It's legal that it could not call the non-static member function :(

John
  • 2,963
  • 11
  • 33
  • Ah, missed that in the second example. – NathanOliver Jun 10 '22 at 01:50
  • The problem is that `std::make_shared` will call `Foo(num)` via `costruct_at` (implementation defined) and then placement new but that is private. You have to make that a friend or implement a placement new member function. – Goswin von Brederlow Jun 10 '22 at 01:59
  • @NathanOliver Since the constructor is marked as private, so a simple static member function seems could not achieve the goal. – John Jun 10 '22 at 01:59
  • Another alternative is to have a private `class Priv;` and public `Foo(int num, Priv)` – Goswin von Brederlow Jun 10 '22 at 02:01
  • @GoswinvonBrederlow "make that a friend or implement a placement new member function. " How? Could you please show me an example code? – John Jun 10 '22 at 02:01
  • You are using `make_shared` which is actually trying to construct the object. related/dupe: https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const – NathanOliver Jun 10 '22 at 02:02
  • @John Sorry, I tried for a bit but making template functions friends is a pain and the insane type definitions in the stl don't make it easier. And scratch the part about placement new, the `costruct_at` calls `::new` which I think you can't override. That just leaves the `class Priv;` way to make a public constructor that only the class can call. – Goswin von Brederlow Jun 10 '22 at 03:06

1 Answers1

2

You can make the shared yourself. Will this work for you?

#include <iostream>
#include <memory>
 
class Foo : public std::enable_shared_from_this<Foo> {
private:     //the user should construct an instance through the constructor below.                    
    Foo(int num):num_(num) { std::cout << "Foo::Foo\n"; }
public:
    ~Foo() { std::cout << "Foo::~Foo\n"; } 
    static std::shared_ptr<Foo> Create() {
        Foo *foo = new Foo(5);
        return std::shared_ptr<Foo>(foo);
    }
private:
    int num_;
};
 
int main() {
    auto pf = Foo::Create(); 
}
Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
  • Thank you so much. I never thought this solution before. What confuses me is that [why `Foo *foo = new Foo(5);` compiles, whereas `std::make_shared(5)` does not](https://godbolt.org/z/3ajvbzcKv). Please pay attention to the comment in the link. – John Jun 10 '22 at 04:14
  • It works because `Create` is a function of the class and `std::make_shared` and `std::construct_at` are not. – Goswin von Brederlow Jun 10 '22 at 04:26
  • "It works because Create is a function of the class ". Even if is a static member function? It's out of my expectation. And I think your solution is much simpler than [this one](https://stackoverflow.com/a/8147326/13611002). How do you think about it? – John Jun 10 '22 at 04:34
  • The private struct trick works in other places where you might not have a choice to call the constructor directly. For example with `std::vector::emplace_back()`. But here either way works. – Goswin von Brederlow Jun 10 '22 at 04:53
  • And I think your solution is much simpler than [this one](https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const/8147326#8147326). How do you think about it? `std::vector::emplace_back()`? Could you please show me a simple example. I can't really get your idea. – John Jun 10 '22 at 04:54