Как вызвать:std:: make shared в классе только с защищенными или частными конструкторами?
у меня есть этот код, который не работает, но я думаю, цель ясна:
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
. Есть ли способ сделать это?
14 ответов:
ответ вероятно, лучше, и тот, который я, вероятно, приму. Но я также придумал метод, который уродливее, но все еще позволяет все еще быть встроенным и не требует производного класса:
#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: Я изменил это, чтобы было ясно, что эта идея четко и просто расширяется для конструкторов, которые принимают аргументы, потому что другие люди давали ответы в этом направлении и, казалось, путались этот.
глядя на требования к
std::make_shared
в 20.7.2.2.6 shared_ptr creation [util.smartptr.общий.создать], пункт 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>(); }
возможно, самое простое решение. На основе предыдущего ответ Мохит Арон и включение предложения 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() {} };
вот аккуратное решение для этого:
#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>(); }
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_; };
поскольку мне не понравились уже предоставленные ответы, я решил искать и нашел решение, которое не является таким же общим, как предыдущие ответы, но мне это нравится больше(tm). Оглядываясь назад, это не намного лучше, чем тот, который предоставляет Omnifarius, но могут быть и другие люди, которым это тоже нравится :)
это не придумано мной, но это идея Джонатана Уэйкли (разработчик 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) с пользовательским распределителем, который объявлен другом классу с помощью частного конструктора.
пример из ОП будет выглядеть так:
#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 с помощью MyAlloc, в этом нет способа создать A или B помимо метода create ().
#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> #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, возможно, потребуется настройка для разных платформ.
Если вы также хотите включить конструктор, который принимает аргументы, это может немного помочь.
#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"); }
В идеале, я думаю, идеальное решение потребует дополнений к стандарту C++. Эндрю Шеплер предлагает следующее:
(Go здесь для всего потока)
мы можем позаимствовать идею из 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; }; }
использование
Если / когда вышеуказанное будет добавлено к стандарту, мы просто сделаем:
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 Google.
есть более волосатая и интересная проблема, которая возникает, когда у вас есть два строго связанных класса A и B, которые работают вместе.
скажем "мастер-класс" и в его "рабом". Если вы хотите ограничить количество экземпляров б только, ты станешь конструктором Б личное, и друг в такой
class B { public: // B your methods... private: B(); friend class A; };
к сожалению называя
std::make_shared<B>()
из методA
заставит компилятор жаловаться наB::B()
частные.мой решение этой проблемы заключается в создании публичного
Pass
фиктивный класс (так же, какnullptr_t
) внутриB
который имеет частный конструктор и дружит с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); } };
корень проблемы заключается в том, что если функция или класс, который вы друг, делает вызовы более низкого уровня для вашего конструктора, они тоже должны быть связаны. std:: make_shared-это не функция, которая на самом деле вызывает ваш конструктор, поэтому friending не имеет никакого значения.
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 лет, я заплатил большую цену во времени, боли и побочных эффектах, чтобы исправить это, когда это было сделано неправильно много лет назад.
вы можете использовать это:
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() {}; };
#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; }