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

时间:2011-11-16 05:11:36

标签: c++ c++11 shared-ptr

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

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();
}

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

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

这条消息基本上是说来自::std::make_shared的模板实例化堆栈中的一些随机方法无法访问构造函数,因为它受到保护。

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

16 个答案:

答案 0 :(得分:99)

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

#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);
}

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

答案 1 :(得分:65)

查看20.7.2.2.6 shared_ptr创建[util.smartptr.shared.create]中std::make_shared的要求,第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>();
}

答案 2 :(得分:58)

可能是最简单的解决方案。基于Mohit Aron之前的answer并结合了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() {}  
};

答案 3 :(得分:23)

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

#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>();
}

答案 4 :(得分:11)

这个怎么样?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}

答案 5 :(得分:10)

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_;
};

答案 6 :(得分: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;
}

基于我正在使用的实用程序的更复杂的示例。有了这个,我无法使用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;
}

答案 7 :(得分:3)

我意识到这个线程相当陈旧,但我找到了一个答案,它不需要继承或额外的构造函数参数,这是我在其他地方看不到的。它虽然不便携:

#include <memory>

#if defined(__cplusplus) && __cplusplus >= 201103L
#define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator<test>::construct<test>(test*);
#elif defined(_WIN32) || defined(WIN32)
#if defined(_MSC_VER) && _MSC_VER >= 1800
#define ALLOW_MAKE_SHARED(x) friend class std::_Ref_count_obj;
#else
#error msc version does not suport c++11
#endif
#else
#error implement for platform
#endif

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

};
int main() {
    std::shared_ptr<test> t(test::create());
}

我已经在Windows和Linux上测试过,可能需要针对不同平台进行调整。

答案 8 :(得分:3)

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

(将here用于整个线程)

  

我们可以借鉴boost :: iterator_core_access的想法。我提议   新的<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <AuthenticateUser xmlns="TestWService"> <UserName>testadmin</UserName> <Password>pass</Password> </AuthenticateUser> </soap:Header> <soap:Body> <AllCountriesInfo xmlns="TestWService" /> </soap:Body> </soap:Envelope> 类,没有公开或   受保护的成员,并为   std :: make_shared(args ...)和std :: alloc_shared(a,args ...),   表达式:: new(pv)T(forward(args)...)和ptr->〜T()必须为   在std :: shared_ptr_access的上下文中格式正确。

     

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;
    };
}

如果这听起来像是对标准的重要补充,请随时将2美分添加到链接的isocpp Google网上论坛。

答案 9 :(得分:2)

当你有两个严格相关的A和B类一起工作时会发生更多毛茸茸和有趣的问题。

说A是“大师班”,B是“奴隶”。如果你想将B的实例化仅限制为A,你可以将B的构造函数设为私有,将朋友B设为A,就像这样

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

private:
    B();
    friend class A;
};

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

我的解决方案是在Pass内创建一个公共nullptr_t虚拟类(就像B),该类具有私有构造函数,并且是A的朋友并且生成{ {1}}构造函数public并将B添加到其参数中,如下所示。

Pass

答案 10 :(得分:2)

如果你还想启用一个带参数的构造函数,这可能会有所帮助。

#include <memory>
#include <utility>

template<typename S>
struct enable_make : public S
{
    template<typename... T>
    enable_make(T&&... t)
        : S(std::forward<T>(t)...)
    {
    }
};

class foo
{
public:
    static std::unique_ptr<foo> create(std::unique_ptr<int> u, char const* s)
    {
        return std::make_unique<enable_make<foo>>(std::move(u), s);
    }
protected:
    foo(std::unique_ptr<int> u, char const* s)
    {
    }
};

void test()
{
    auto fp = foo::create(std::make_unique<int>(3), "asdf");
}

答案 11 :(得分:2)

[编辑]我通读了上面关于标准std::shared_ptr_access<>提议的主题。里面有一个响应,指出对std::allocate_shared<>的修复及其用法的示例。我已将其调整为下面的工厂模板,并在gcc C ++ 11/14/17下对其进行了测试。它也可以与std::enable_shared_from_this<>一起使用,因此显然比此答案中我的原始解决方案更可取。在这里...

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        return std::allocate_shared<T>(Alloc<T>(), std::forward<A>(args)...);
    }
private:
    template<typename T>
    struct Alloc : std::allocator<T> {
        template<typename U, typename... A>
        void construct(U* ptr, A&&... args) {
            new(ptr) U(std::forward<A>(args)...);
        }
        template<typename U>
        void destroy(U* ptr) {
            ptr->~U();
        }
    };  
};

class X final : public std::enable_shared_from_this<X> {
    friend class Factory;
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(int) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto p1 = Factory::make_shared<X>(42);
    auto p2 = p1->shared_from_this();
    std::cout << "p1=" << p1 << "\n"
              << "p2=" << p2 << "\n"
              << "count=" << p1.use_count() << "\n";
}

[原件]我找到了使用共享指针别名构造函数的解决方案。它允许ctor和dtor都是私有的,并且可以使用最终的说明符。

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        return std::shared_ptr<T>(ptr, &ptr->type);
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
};

class X final {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = Factory::make_shared<X>(42);
}

请注意,以上方法不适用于std::enable_shared_from_this<>,因为最初的std::shared_ptr<>是包装器而不是类型本身。我们可以使用与工厂兼容的等效类来解决这个问题。

#include <iostream>
#include <memory>

template<typename T>
class EnableShared {
    friend class Factory;  // factory access
public:
    std::shared_ptr<T> shared_from_this() { return weak.lock(); }
protected:
    EnableShared() = default;
    virtual ~EnableShared() = default;
    EnableShared<T>& operator=(const EnableShared<T>&) { return *this; }  // no slicing
private:
    std::weak_ptr<T> weak;
};

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        auto alt = std::shared_ptr<T>(ptr, &ptr->type);
        assign(std::is_base_of<EnableShared<T>, T>(), alt);
        return alt;
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
    template<typename T>
    static void assign(std::true_type, const std::shared_ptr<T>& ptr) {
        ptr->weak = ptr;
    }
    template<typename T>
    static void assign(std::false_type, const std::shared_ptr<T>&) {}
};

class X final : public EnableShared<X> {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = ptr1->shared_from_this();
    std::cout << "ptr1=" << ptr1.get() << "\nptr2=" << ptr2.get() << "\n";
}

最后,有人说clang抱怨Factory :: Type在用作朋友时是私有的,因此,在这种情况下,请将其公开。暴露无害。

答案 12 :(得分:0)

问题的根源在于,如果您的朋友所使用的函数或类对构造函数进行了较低级别的调用,则也必须使其成为朋友。 std :: make_shared不是真正在调用构造函数的函数,因此与之友好并没有区别。

Value: armv7

std :: _ Ref_count_obj实际上是在调用您的构造函数,因此它需要成为朋友。由于有点晦涩,所以我使用宏

class A;
typedef std::shared_ptr<A> APtr;
class A
{
    template<class T>
    friend class std::_Ref_count_obj;
public:
    APtr create()
    {
        return std::make_shared<A>();
    }
private:
    A()
    {}
};

然后,您的类声明看起来很简单。如果愿意,可以创建一个宏来声明ptr和类。

#define SHARED_PTR_DECL(T) \
class T; \
typedef std::shared_ptr<T> ##T##Ptr;

#define FRIEND_STD_MAKE_SHARED \
template<class T> \
friend class std::_Ref_count_obj;

这实际上是一个重要问题。 为了使代码易于维护,您需要隐藏尽可能多的实现。

SHARED_PTR_DECL(B);
class B
{
    FRIEND_STD_MAKE_SHARED
public:
    BPtr create()
    {
        return std::make_shared<B>();
    }
private:
    B()
    {}
};

隐藏了您如何处理智能指针的方式,您必须确保使用typedef。但是,如果您始终必须使用make_shared创建一个,则会达到目的。

上面的示例强制使用类的代码使用智能指针构造函数,这意味着,如果切换到新的智能指针风格,则会更改类声明,并且很有可能完成。不要以为下一个老板或项目会在某天使用stl,boost等进行更改的计划。

这样做已经将近30年了,我付出了巨大的时间,痛苦和副作用来修复几年前做错的事。

答案 13 :(得分:0)

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

为此,并在所有使用相似方法的现有答案的基础上,我介绍一下这个小块:

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 )... );
}

答案 14 :(得分:-3)

您可以使用:

class CVal
{
    friend std::shared_ptr<CVal>;
    friend std::_Ref_count<CVal>;
public:
    static shared_ptr<CVal> create()
    {
        shared_ptr<CVal> ret_sCVal(new CVal());
        return ret_sCVal;
    }

protected:
    CVal() {};
    ~CVal() {};
};

答案 15 :(得分:-3)

#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
private:
    A(){}
    explicit A(int a):m_a(a){}
public:
    template <typename... Args>
    static std::shared_ptr<A> create(Args &&... args)
    {
        class make_shared_enabler : public A
        {
        public:
            make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...){}
        };
        return std::make_shared<make_shared_enabler>(std::forward<Args>(args)...);
    }

    int val() const
    {
        return m_a;
    }
private:
    int m_a=0;
};

int main(int, char **)
{
    std::shared_ptr<A> a0=A::create();
    std::shared_ptr<A> a1=A::create(10);
    std::cout << a0->val() << " " << a1->val() << std::endl;
    return 0;
}