如何在只有受保护或私有构造函数的类上调用:: std :: make_shared?

Omn*_*ous 162 c++ shared-ptr c++11

我有这个代码不起作用,但我认为意图很明确:

testmakeshared.cpp

#include <memory>

class A {
 public:
   static ::std::shared_ptr<A> create() {
      return ::std::make_shared<A>();
   }

 protected:
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}
Run Code Online (Sandbox Code Playgroud)

但是我在编译时遇到了这个错误:

g++ -std=c++0x -march=native -mtune=native -O3 -Wall testmakeshared.cpp
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:52:0,
                 from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/memory:86,
                 from testmakeshared.cpp:1:
testmakeshared.cpp: In constructor ‘std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc) [with _Tp = A, _Alloc = std::allocator<A>, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’:
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:518:8:   instantiated from ‘std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:986:35:   instantiated from ‘std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:313:64:   instantiated from ‘std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:531:39:   instantiated from ‘std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:547:42:   instantiated from ‘std::shared_ptr<_Tp1> std::make_shared(_Args&& ...) [with _Tp = A, _Args = {}]’
testmakeshared.cpp:6:40:   instantiated from here
testmakeshared.cpp:10:8: error: ‘A::A()’ is protected
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:400:2: error: within this context

Compilation exited abnormally with code 1 at Tue Nov 15 07:32:58
Run Code Online (Sandbox Code Playgroud)

此消息基本上是说模板实例化堆栈中的某些随机方法向下::std::make_shared无法访问构造函数,因为它受到保护.

但是我真的想要同时使用这两个::std::make_shared并阻止任何人制作这个类没有指向的类的对象::std::shared_ptr.有没有办法实现这个目标?

Omn*_*ous 104

这个答案可能更好,而且我可能会接受.但我也想出了一个更丑陋的方法,但仍然让所有内容仍然是内联的,并且不需要派生类:

#include <memory>
#include <string>

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template <typename... T>
   static ::std::shared_ptr<A> create(T &&...args) {
      return ::std::make_shared<A>(this_is_private{0},
                                   ::std::forward<T>(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

::std::shared_ptr<A> bar()
{
   return A::create("George", 5);
}

::std::shared_ptr<A> errors()
{
   ::std::shared_ptr<A> retval;

   // Each of these assignments to retval properly generates errors.
   retval = A::create("George");
   retval = new A(A::this_is_private{0});
   return ::std::move(retval);
}
Run Code Online (Sandbox Code Playgroud)

编辑2017-01-06:我改变了这一点,明确表示这个想法清楚而简单地可以扩展到带有参数的构造函数,因为其他人在这些方面提供了答案并且似乎对此感到困惑.

  • 实际上,我是那些仅用作*键*的无意义结构的忠实粉丝.我更喜欢Luc的解决方案,但这可能是我对抗继承的原因. (12认同)
  • @Berkus:然后把它变为`protected`而不是`private`.并且通过"它",我指的是`this_is_private`类,在这种情况下可能应该重命名.我通常在我的代码中称它为"constructor_access". (3认同)
  • Stefan,如果你给'this_is_private`一个私人监护人,你可以让A级成为朋友.似乎关闭了漏洞. (3认同)
  • 同意,我也更喜欢这个. (2认同)
  • 这个问题已经有足够的替代答案(尽管其中许多似乎是同一主题的重复变体)。我在其他领域使用了 PassKey 习惯用法,我发现这里两个投票最高的答案实际上结合得很好。我添加了一个派生自主类的私有“make_shared_enabler”类,但主类的构造函数采用该“make_shared_enabler”类的 PassKey。这允许我通过保护构造函数来隐藏公共 API 的构造函数,同时通过 PassKey 禁止(额外的)子类化。 (2认同)
  • @Omnifarious:“this_is_private” ctor 中的“int”有什么意义?为什么不简单地“显式 this_is_private() =default;” (2认同)

Luc*_*ton 75

查看std::make_shared20.7.2.2.6 shared_ptr创建[util.smartptr.shared.create]中的要求,第1段:

要求:表达式::new (pv) T(std::forward<Args>(args)...),其中pv有类型void*和指向适合于容纳类型对象的存储T,应该很好地形成.A应是分配者(17.6.3.5).复制构造函数和析构函数A不应抛出异常.

由于要求是无条件地根据该表达式指定的,并且不考虑范围之类的东西,我认为友谊这样的技巧是正确的.

一个简单的解决方案是派生自A.这不需要制作A接口甚至是多态类型.

// interface in header
std::shared_ptr<A> make_a();

// implementation in source
namespace {

struct concrete_A: public A {};

} // namespace

std::shared_ptr<A>
make_a()
{
    return std::make_shared<concrete_A>();
}
Run Code Online (Sandbox Code Playgroud)

  • 啊,这是因为`shared_ptr`在实例化时存储了一个删除器,如果你使用`make_shared`,删除器绝对必须使用正确的类型. (8认同)
  • 我喜欢这种方法(自己使用),但是您确实需要一个虚拟析构函数。它可以很好地扩展到带有参数的构造函数(只需提供直通构造函数)。如果使用的是_protected_而不是_private_,则可以使标题用户完全看不到它。 (2认同)

Mar*_*ley 67

可能是最简单的解决方案.根据Mohit Aron 先前的回答并结合dlf的建议.

#include <memory>

class A
{
public:
    static std::shared_ptr<A> create()
    {
        struct make_shared_enabler : public A {};

        return std::make_shared<make_shared_enabler>();
    }

private:
    A() {}  
};
Run Code Online (Sandbox Code Playgroud)

  • 如果`A`有非默认构造函数,你还需要公开它们:`struct make_shared_enabler:public A {template <typename ... Args> make_shared_enabler(Args && ... args):A(std :: forward <Args >(args)...){}};`.这使得`A`的所有私有构造函数都可见为`make_shared_enabler`构造函数.使用构造函数继承功能(`使用A :: A;`)似乎没有帮助,因为构造函数仍然是私有的. (4认同)
  • @anton_rh:您不能向内部类添加模板参数。请参阅[此处](http://stackoverflow.com/questions/4299314/member-template-in-local-class)。 (3认同)
  • 嗯...看来你是对的。在我的情况下,结构不是本地结构,而是私有结构:`class A {... private:struct A_shared_enabler; }; A :: A_shared_enabler类:公共A {...}`。参见此处http://cpp.sh/65qbr。 (2认同)

小智 25

这是一个很好的解决方案:

#include <memory>

class A {
   public:
     static shared_ptr<A> Create();

   private:
     A() {}

     struct MakeSharedEnabler;   
 };

struct A::MakeSharedEnabler : public A {
    MakeSharedEnabler() : A() {
    }
};

shared_ptr<A> A::Create() {
    return make_shared<MakeSharedEnabler>();
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个.通过在`A :: Create()`中本地定义`MakeSharedEnabler`可以使它更简单一些. (3认同)

alp*_*pha 11

struct A {
public:
  template<typename ...Arg> std::shared_ptr<A> static create(Arg&&...arg) {
    struct EnableMakeShared : public A {
      EnableMakeShared(Arg&&...arg) :A(std::forward<Arg>(arg)...) {}
    };
    return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
  }
  void dump() const {
    std::cout << a_ << std::endl;
  }
private:
  A(int a) : a_(a) {}
  A(int i, int j) : a_(i + j) {}
  A(std::string const& a) : a_(a.size()) {}
  int a_;
};
Run Code Online (Sandbox Code Playgroud)


Sea*_*ean 10

这个怎么样?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
Run Code Online (Sandbox Code Playgroud)

  • 这很好用.但是`:: std :: make_shared`除了简单地将shared_ptr变成某种东西之外还有其他功能.它将引用计数与对象一起分配,使它们彼此靠近.我真的非常想使用`:: std :: make_shared`. (13认同)
  • 这实际上是最直接的方法,即使问题并不是真正的问题.make_shared确实有一些很好的特性,我尝试尽可能地使用它,但在这种情况下,make_shared的运行时性能优势似乎不会超过额外的代码复杂性和使用它的实际需要的仪式.如果你真的需要make_shared的性能然后发疯,但不要忽视使用shared_ptr的构造函数的简单性. (7认同)
  • @smac89 - https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared#Notes - 请参阅注释部分,第一个要点。这是标准中建议的实现。 (2认同)

Mat*_*hew 9

我遇到了同样的问题,但现有的答案都不是真正令人满意,因为我需要将参数传递给受保护的构造函数。此外,我需要对几个类执行此操作,每个类都采用不同的参数。

为此,在几个都使用类似方法的现有答案的基础上,我提出了这个小金点:

template < typename Object, typename... Args >
inline std::shared_ptr< Object >
protected_make_shared( Args&&... args )
{
  struct helper : public Object
  {
    helper( Args&&... args )
      : Object{ std::forward< Args >( args )... }
    {}
  };

  return std::make_shared< helper >( std::forward< Args >( args )... );
}
Run Code Online (Sandbox Code Playgroud)

  • 这非常酷,因为它不会让课堂变得混乱。谢谢。如果我将这个 `protected_make_shared` 设为全局命名空间中的公共函数,我必须将其作为友元添加到类中,如下所示: `template&lt;typename Object, typename... Args&gt;friend shared_ptr&lt;Object&gt; protected_make_shared(Args&amp;&amp;.. .args);`. 但如果这仅由一个类使用,您可以将其添加为(公共)类成员“静态内联”。 (2认同)
  • 该解决方案的缺点是“protected_make_shared”可以从任何地方调用,包括从与“Object”不是友元的类中调用。 (2认同)

Zso*_*nyi 8

由于我不喜欢已提供的答案,我决定搜索并找到一个不像以前的答案那样通用的解决方案,但我更喜欢它(tm).回想起来,它并不比Omnifarius提供的更好,但也可能有其他人也喜欢它:)

这不是我发明的,而是Jonathan Wakely(GCC开发人员)的想法.

不幸的是,它不适用于所有编译器,因为它依赖于std :: allocate_shared实现中的一个小变化.但是,此更改现在是标准库的建议更新,因此将来可能会得到所有编译器的支持.它适用于GCC 4.7.

C++标准库工作组更改请求位于:http: //lwg.github.com/issues/lwg-active.html#2070

具有示例用法的GCC补丁如下:http: //old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

该解决方案的作用是将std :: allocate_shared(而不是std :: make_shared)与自定义分配器一起使用,该自定义分配器被声明为具有私有构造函数的类的朋友.

OP中的示例如下所示:

#include <memory>

template<typename Private>
struct MyAlloc : std::allocator<Private>
{
    void construct(void* p) { ::new(p) Private(); }
};

class A {
    public:
        static ::std::shared_ptr<A> create() {
            return ::std::allocate_shared<A>(MyAlloc<A>());
        }

    protected:
        A() {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;

        friend struct MyAlloc<A>;
};

int main() {
    auto p = A::create();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

一个更复杂的例子,它基于我正在研究的实用程序.有了这个,我无法使用Luc的解决方案.但Omnifarius的那个可以改编.不是说在前面的例子中,每个人都可以使用MyAlloc创建一个A对象,除了create()方法之外,没有办法创建A或B.

#include <memory>

template<typename T>
class safe_enable_shared_from_this : public std::enable_shared_from_this<T>
{
    public:
    template<typename... _Args>
        static ::std::shared_ptr<T> create(_Args&&... p_args) {
            return ::std::allocate_shared<T>(Alloc(), std::forward<_Args>(p_args)...);
        }

    protected:
    struct Alloc : std::allocator<T>
    {  
        template<typename _Up, typename... _Args>
        void construct(_Up* __p, _Args&&... __args)
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    };
    safe_enable_shared_from_this(const safe_enable_shared_from_this&) = delete;
    safe_enable_shared_from_this& operator=(const safe_enable_shared_from_this&) = delete;
};

class A : public safe_enable_shared_from_this<A> {
    private:
        A() {}
        friend struct safe_enable_shared_from_this<A>::Alloc;
};

class B : public safe_enable_shared_from_this<B> {
    private:
        B(int v) {}
        friend struct safe_enable_shared_from_this<B>::Alloc;
};

int main() {
    auto a = A::create();
    auto b = B::create(5);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Bor*_*ein 7

理想情况下,我认为完美的解决方案需要对 C++ 标准进行补充。Andrew Schepler 提出以下建议:

(去这里查看整个线程)

我们可以从 boost::iterator_core_access 中借用一个想法。我提出一个std::shared_ptr_access没有公共或受保护成员的新类,并为 std::make_shared(args...) 和 std::alloc_shared(a, args...) 指定表达式 ::new(pv) T (forward(args)...) 和 ptr->~T() 在 std::shared_ptr_access 的上下文中必须是良构的。

std::shared_ptr_access 的实现可能如下所示:

namespace std {
    class shared_ptr_access
    {
        template <typename _T, typename ... _Args>
        static _T* __construct(void* __pv, _Args&& ... __args)
        { return ::new(__pv) _T(forward<_Args>(__args)...); }

        template <typename _T>
        static void __destroy(_T* __ptr) { __ptr->~_T(); }

        template <typename _T, typename _A>
        friend class __shared_ptr_storage;
    };
}
Run Code Online (Sandbox Code Playgroud)

用法

如果/当将上述内容添加到标准中时,我们只需执行以下操作:

class A {
public:
   static std::shared_ptr<A> create() {
      return std::make_shared<A>();
   }

 protected:
   friend class std::shared_ptr_access;
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};
Run Code Online (Sandbox Code Playgroud)

如果这对您来说也是标准的重要补充,请随时将您的 2 美分添加到链接的 isocpp Google 群组中。


kee*_*bus 5

当你有两个严格相关的类 A 和 B 一起工作时,就会出现一个更棘手、更有趣的问题。

说A是“主人阶级”,B是“奴隶”。如果你想限制 B 的实例化仅限于 A,你可以将 B 的构造函数设为私有,并将 B 设为 A 的友元,如下所示

class B
{
public:
    // B your methods...

private:
    B();
    friend class A;
};
Run Code Online (Sandbox Code Playgroud)

std::make_shared<B>()不幸的是,从 的方法调用A会使编译器抱怨它B::B()是私有的。

我的解决方案是在内部创建一个公共Pass虚拟类(就像nullptr_tB,它具有私有构造函数并且是朋友,A并使B的构造函数公开并添加Pass到其参数中,如下所示。

class B
{
public:
  class Pass
  {
    Pass() {}
    friend class A;
  };

  B(Pass, int someArgument)
  {
  }
};

class A
{
public:
  A()
  {
    // This is valid
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

class C
{
public:
  C()
  {
    // This is not
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};
Run Code Online (Sandbox Code Playgroud)