我有这样的代码,但我认为意图是明确的:

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和防止任何人创建这个类的对象不是由a::std::shared_ptr指向的。有什么办法可以做到吗?


当前回答

查看20.7.2.2.6 shared_ptr创建[util.smartptr.shared]中std::make_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>();
}

其他回答

因为我不喜欢已经提供的答案,所以我决定继续搜索,并找到了一个解决方案,它不像之前的答案那么通用,但我更喜欢它(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;
}

一个基于我正在使用的实用程序的更复杂的示例。在这种情况下,我不能使用卢克的解决方案。但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;
}

这个答案可能更好,也是我可能会接受的答案。但我也提出了一个更丑的方法,但仍然让一切仍然是内联的,不需要一个派生类:

#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:我对此进行了更改,以清楚地表明,这个思想可以清楚地、简单地扩展到接受参数的构造函数,因为其他人正在按照这些思路提供答案,并且似乎对此感到困惑。

class A  {
public:
 static std::shared_ptr<A> getA() {
   std::shared_ptr<A> a = nullptr;
   a.reset(new A());
   return a;
 }

private:
  A() {}
};

由于std::make_shared不能调用私有构造函数,我们使用new手动创建A的实例。然后使用reset将shared_ptr设置为指向新的A对象。你不必担心泄露内存,shared_ptr会为你删除A。

查看20.7.2.2.6 shared_ptr创建[util.smartptr.shared]中std::make_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>();
}
#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;
}