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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8147027/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:05:09  来源:igfitidea点击:

How do I call ::std::make_shared on a class with only protected or private constructors?

c++c++11shared-ptr

提问by Omnifarious

I have this code that doesn't work, but I think the intent is clear:

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

testmakeshared.cpp

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

But I get this error when I compile it:

但是我编译的时候出现这个错误:

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

This message is basically saying that some random method way down in the template instantiation stack from ::std::make_sharedcan't access the constructor because it's protected.

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

But I really want to use both ::std::make_sharedand prevent anybody from making an object of this class that isn't pointed at by a ::std::shared_ptr. Is there any way to accomplish this?

但我真的很想同时使用两者,::std::make_shared并防止任何人创建一个::std::shared_ptr. 有什么办法可以做到这一点吗?

采纳答案by Omnifarious

This answeris probably better, and the one I'll likely accept. But I also came up with a method that's uglier, but does still let everything still be inline and doesn't require a derived class:

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

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

Edit 2017-01-06:I changed this to make it clear that this idea is clearly and simply extensible to constructors that take arguments because other people were providing answers along those lines and seemed confused about this.

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

回答by Luc Danton

Looking at the requirements for std::make_sharedin 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], paragraph 1:

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

Requires:The expression ::new (pv) T(std::forward<Args>(args)...), where pvhas type void*and points to storage suitable to hold an object of type T, shall be well formed. Ashall be an allocator (17.6.3.5). The copy constructor and destructor of Ashall not throw exceptions.

要求:表达式::new (pv) T(std::forward<Args>(args)...),其中pv具有类型void*并指向适合保存类型对象的存储T,应格式良好。A应为分配器 (17.6.3.5)。的复制构造函数和析构函数A不得抛出异常。

Since the requirement is unconditionally specified in terms of that expression and things like scope aren't taken into account, I think tricks like friendship are right out.

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

A simple solution is to derive from A. This needn't require making Aan interface or even a polymorphic type.

一个简单的解决方案是从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>();
}

回答by Mark Tolley

Possibly the simplest solution. Based on the previous answerby Mohit Aron and incorporating dlf's suggestion.

可能是最简单的解决方案。基于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() {}  
};

回答by Mohit Aron

Here's a neat solution for this:

这是一个巧妙的解决方案:

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

回答by alpha

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

回答by Sean

How about this?

这个怎么样?

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

回答by Zsolt Rizsányi

Since I didn't like the already provided answers I decided to search on and found a solution that is not as generic as the previous answers but I like it better(tm). In retrospect it is not much nicer than the one provided by Omnifarius but there could be other people who like it too :)

因为我不喜欢已经提供的答案,所以我决定搜索并找到一个不像以前的答案那么通用的解决方案,但我更喜欢它(tm)。回想起来,它并不比 Omnifarius 提供的好多少,但可能还有其他人也喜欢它 :)

This is not invented by me, but it is the idea of Jonathan Wakely (GCC developer).

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

Unfortunately it does not work with all the compilers because it relies on a small change in std::allocate_shared implementation. But this change is now a proposed update for the standard libraries, so it might get supported by all the compilers in the future. It works on GCC 4.7.

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

C++ standard Library Working Group change request is here: http://lwg.github.com/issues/lwg-active.html#2070

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

The GCC patch with an example usage is here: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

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

The solution works on the idea to use std::allocate_shared (instead of std::make_shared) with a custom allocator that is declared friend to the class with the private constructor.

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

The example from the OP would look like this:

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

A more complex example that is based on the utility I'm working on. With this I could not use Luc's solution. But the one by Omnifarius could be adapted. Not that while in the previous example everybody can create an A object using the MyAlloc in this one there is not way to create A or B besides the create() method.

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

回答by Boris Dalstein

Ideally, I think the perfect solution would require additions to the C++ standard. Andrew Schepler proposes the following:

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

(Go herefor the whole thread)

(去这里查看整个线程)

we can borrow an idea from boost::iterator_core_access. I propose a new class std::shared_ptr_accesswith no public or protected members, and to specify that for std::make_shared(args...) and std::alloc_shared(a, args...), the expressions ::new(pv) T(forward(args)...) and ptr->~T() must be well-formed in the context of std::shared_ptr_access.

An implementation of std::shared_ptr_access might look like:

我们可以从 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;
    };
}

Usage

用法

If/when the above is added to the standard, we would simply do:

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

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

If this also sounds like an important addition to the standard to you, feel free to add your 2 cents to the linked isocpp Google Group.

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

回答by ashleysmithgpu

I realise this thread is rather old, but I found an answer that does not require inheritance or extra arguments to the constructor that I couldn't see elsewhere. It is not portable though:

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

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

I have tested on windows and linux, it may need tweaking for different platforms.

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

回答by user1715587

[Edit] I read through the thread noted above on a standardized std::shared_ptr_access<>proposal. Within there was a response noting a fix to std::allocate_shared<>and an example of its use. I've adapted it to a factory template below, and tested it under gcc C++11/14/17. It works with std::enable_shared_from_this<>as well, so would obviously be preferable to my original solution in this answer. Here it is...

[编辑] 我通读了上面关于标准化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";
}


[Orig] I found a solution using the shared pointer aliasing constructor. It allows both the ctor and dtor to be private, as well as use of the final specifier.

[Orig] 我找到了一个使用共享指针别名构造函数的解决方案。它允许 ctor 和 dtor 都是私有的,也允许使用 final 说明符。

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

Note that the approach above doesn't play well with std::enable_shared_from_this<>because the initial std::shared_ptr<>is to the wrapper and not the type itself. We can address this with an equivalent class that is compatible with the factory...

请注意,上面的方法不能很好地使用,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";
}

Lastly, somebody said clang complained about Factory::Type being private when used as a friend, so just make it public if that's the case. Exposing it does no harm.

最后,有人说 clang 抱怨 Factory::Type 在用作朋友时是私有的,所以如果是这种情况,只需将其公开。暴露它没有坏处。