我有这样的代码,但我认为意图是明确的:
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指向的。有什么办法可以做到吗?
因为我不喜欢已经提供的答案,所以我决定继续搜索,并找到了一个解决方案,它不像之前的答案那么通用,但我更喜欢它(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;
}
当两个严格相关的类a和类B一起工作时,会出现一个更麻烦也更有趣的问题。
假设A是“主类”,B是“从类”。如果你想将B的实例化限制为A,你可以将B的构造函数设为private,并将B设为A的友例
class B
{
public:
// B your methods...
private:
B();
friend class A;
};
不幸的是,从a的方法调用std::make_shared<B>()将使编译器抱怨B::B()是私有的。
我对此的解决方案是在B内部创建一个公共Pass虚拟类(就像nullptr_t一样),它有私有构造函数,与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);
}
};
理想情况下,我认为完美的解决方案是需要添加到c++标准中。Andrew Schepler提出以下建议:
(点击这里查看整篇文章)
我们可以借用boost::iterator_core_access中的思想。我建议
一个新类std::shared_ptr_access,没有public或
受保护的成员,并指定为
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;
};
}
使用
如果/当将上述内容添加到标准中,我们将简单地做到:
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;
};
如果这听起来也是对标准的重要补充,请随时将您的2美分添加到链接的isocpp谷歌组中。
问题的根源在于,如果你加为好友的函数或类对你的构造函数进行低级调用,那么它们也必须加为好友。Std::make_shared并不是真正调用构造函数的函数,因此添加为好友并没有什么区别。
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()
{}
};
std::_Ref_count_obj实际上是在调用你的构造函数,所以它需要是一个友函数。因为这有点晦涩,所以我使用宏
#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;
然后你的类声明看起来相当简单。如果你愿意,你可以创建一个宏来声明ptr和类。
SHARED_PTR_DECL(B);
class B
{
FRIEND_STD_MAKE_SHARED
public:
BPtr create()
{
return std::make_shared<B>();
}
private:
B()
{}
};
这实际上是一个很重要的问题。
为了使代码可维护、可移植,您需要隐藏尽可能多的实现。
typedef std::shared_ptr<A> APtr;
隐藏了你如何处理智能指针,你必须确保使用你的typedef。但是如果您总是必须使用make_shared来创建一个,这就违背了目的。
上面的示例强制使用类的代码使用智能指针构造函数,这意味着如果您切换到新的智能指针类型,您只需更改类声明,就有很大的机会完成任务。不要以为你的下一个老板或下一个项目会使用stl、boost等。
做了将近30年,我付出了巨大的时间代价、痛苦和副作用来修复多年前做错的事情。
[编辑]我阅读了上面提到的标准化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]我发现了一个解决方案使用共享指针别名构造函数。它允许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);
}
注意,上面的方法不适用于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在作为朋友使用时是私有的,所以如果是这种情况,就把它设为公共。暴露它没有坏处。
基于CRTP的解决方案,允许对多个类进行分解,很容易启用,并适用于带参数的构造函数。它要求构造函数是受保护的(而不是私有的)。用法有点类似于enable_shared_from_this。它没有破坏受保护关键字的缺点,即使用::make_unique的类必须是友类。灵感来自Mark Tolley的回答。
实现:
template <typename ClassWithProtectedCtor>
class enable_protected_make_unique
{
protected: // important, if public then equivalent to having the constructor public which is what we want to avoid!
template <typename... Args>
static std::unique_ptr<ClassWithProtectedCtor> make_unique(Args &&... args)
{
class make_unique_enabler : public ClassWithProtectedCtor
{
public:
// it's from this line that comes the need to have the constructor protected, not private:
make_unique_enabler(Args &&... args) : ClassWithProtectedCtor(std::forward<Args>(args)...) {}
};
return std::make_unique<make_unique_enabler>(std::forward<Args>(args)...);
}
};
用法示例:
class Factory;
class MyClassWithProtectedCtor : public enable_protected_make_unique<MyClassWithProtectedCtor>
{
friend Factory;
private:
MyClassWithProtectedCtor(int a, double c) {};
}
class Factory
{
std::unique_ptr<MyClassWithProtectedCtor> CreateMyClassWithProtectedCtor(int a, double c)
{
return MyClassWithProtectedCtor::make_unique(a, c);
}
}
您可以用shared替换unique,或者在同一个“使能器”类中合并这两者。
免责声明:我没有在产品代码中测试,可能有缺点(例如当提到MyClassWithProtectedCtor类型时,会出现较长的错误消息)。