Як мені зателефонувати :: std :: make_shared для класу із захищеними або приватними конструкторами?


187

У мене цей код не працює, але я думаю, що намір зрозумілий:

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. Чи є спосіб досягти цього?


Ви можете позначити функцію в глибині душі, якій потрібен конструктор як друг, але це не буде портативно.
Дани

@Dani: Так, було б непогано мати портативне рішення. Але це спрацювало б.
всезнайко

Відповіді:


109

Ця відповідь , мабуть, краща, і я, швидше за все, прийму. Але я також придумав метод, який є більш потворним, але все ж нехай все ще є вбудованим і не вимагає похідного класу:

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

Редагувати 06.01.2017: Я змінив це, щоб зрозуміти, що ця ідея чітко і просто поширюється на конструкторів, які беруть аргументи, тому що інші люди давали відповіді в цих напрямках і, здавалося, збентежені з цього приводу.


14
Насправді я величезний фанат тих безглуздих структур, які використовуються лише як ключі . Я вважаю за краще це рішення Люка, але це може бути моїми ухилами від спадкування.
Матьє М.

2
Домовились, мені це теж подобається.
ildjarn

3
@ Беркус: Тоді зробіть це protectedзамість private. І під «це» я маю на увазі this_is_privateклас, який, можливо, у такому випадку повинен бути перейменований. Зазвичай я називаю це constructor_accessу своєму коді.
далле

1
На жаль, це не працює, якщо ваш конструктор приймає реальні параметри; у цьому випадку ви можете просто передати {}приватний тег, не маючи доступу до імені типу (протестовано з g ++ 4.9.0). Без реальних параметрів він намагається побудувати Aз {}, хоча я поняття не маю, чому і не вдається. Думаю, що конструктор this_is_private приватний та надання статичного методу для його створення виправляє його, оскільки не повинно бути способу доступу до цього методу ззовні, якщо ви не просочите тип у підписі функції члена.
Стефан

3
Стефане, якщо ви дасте this_is_privateприватний ctor, ви можете зробити клас А другом. Здається, закриває лазівку.
Стівен Крамер

78

Розглядаючи вимоги до std::make_sharedстворення 20.7.2.2.6 спільного_ptr створення [util.smartptr.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>();
}

1
О, це дуже розумна відповідь і, можливо, краща за іншу, про яку я думав.
всезнайко

Однак одне питання, чи не видалятиме спільний_ptr A та не конкретний_A, і чи не може це спричинити проблеми?
всезначний

8
Ага, це тому, що shared_ptrзберігає делетер під час інстанції, і якщо ви використовуєте make_sharedделетер, то обов'язково потрібно використовувати правильний тип.
всезнайко

1
@LucDanton Питання не стосується інтерфейсів, оскільки заголовок підказує, що він також просить приватний ctor. Крім того, тому я все одно в цьому питанні. Деякий старий код, який має класи Machiavelli, у якого є приватний ctor та метод створення, що повертає необроблений покажчик, і я намагаюся перетворити їх у розумні покажчики.
zahir

2
Мені подобається такий підхід (використовуючи його сам), але вам потрібен віртуальний деструктор. Він добре поширюється на конструктори з аргументами (просто надайте прохідний конструктор). І якщо ви використовуєте захищений, а не приватний, ви можете зробити його абсолютно непомітним для користувачів заголовка.
Джо Стіл

69

Можливо, найпростіше рішення. На основі попередньої відповіді Мохіта Арона та включення пропозиції 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() {}  
};

5
якщо Aє конструктори не за замовчуванням , вам необхідно буде також піддавати їх: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Це робить усі приватні конструктори Aвидимими як make_shared_enablerконструктори. Використання функції успадкування конструкторів ( using A::A;), здається, тут не допомагає, оскільки конструктори залишаються приватними.
anton_rh

2
@anton_rh: не можна додавати аргументи шаблону до внутрішніх класів. Дивіться тут .
боббель

3
Гм ... Здається, ви праві. У моєму випадку структура не була локальною, а була приватною class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Дивіться тут cpp.sh/65qbr .
anton_rh

Це чудово працює. Чи є шанс зробити це спадковою властивістю, тому цей шаблон не повинен повторюватися багато разів? Особливо мені дуже цікава версія, яка викриває конструктори, що не замовчуються. Версія за замовчуванням "просто" вимагає синтаксичної конструкції, яка замінює A будь-яким класом, який успадковує клас. Мені нічого подібного не відомо, але я би не здивувався, дізнавшись, що воно існує ...
Kjeld Schmidt

30

Ось акуратне рішення для цього:

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

3
Мені подобається це. Це можна зробити трохи простіше, визначивши MakeSharedEnablerлокально всередині A::Create().
dlf

Дивовижна ідея Mohit мені дуже допомогла.
Jnana

12

Як щодо цього?

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

13
Це чудово працює. Але ::std::make_sharedфункціонал є вище і далі - просто зробити спільним_птр щось. Він розподіляє відліку посилань разом з об'єктом, щоб вони розташовувалися близько один до одного. Я дуже, дуже хочу використовувати ::std::make_shared.
Всезнайко

Видалені оператори присвоєння та копіювання забороняють це
Дани

7
Це насправді найпростіший підхід, хоча це питання насправді не було. make_shared має деякі приємні характеристики, і я намагаюся використовувати його там, де це можливо, але в цій ситуації видається цілком ймовірним, що переваги продуктивності роботи make_shared не переважають над зайвою складністю коду і фактично необхідною церемонією для його використання. Якщо вам дійсно потрібна продуктивність make_shared, тоді виходите з розуму, але не нехтуйте простотою простого використання конструктора shared_ptr.
Кевін

Будьте обережні витоку пам'яті , хоча ... побачити це питання stackoverflow.com/a/14837300/2149539
dgmz

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

Це багато в чому те саме, що відповідь Люка Дантона, хоча перетворення його на місцевий клас - приємний штрих. Деякі пояснення супроводу коду можуть зробити це набагато кращою відповіддю.

Як правило, я хочу написати таку маленьку функцію у заголовку, але не у файлі cc. По-друге, на практиці я використовую макрос, який виглядає як шаблон #define SharedPtrCreate (T) <typename ... Arg> .....
alpha

Хороша відповідь. Я навіть ставлю це в макрос, який називається IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

Оскільки мені не сподобалися вже надані відповіді, я вирішив шукати і знайшов рішення, яке не таке загальне, як попередні відповіді, але мені це подобається краще (тм). Зрештою, він не набагато приємніший за той, який надав Омніфарій, але можуть бути і інші люди, яким це теж подобається :)

Це не придумано мною, але це ідея Джонатана Уейклі (розробник GCC).

На жаль, він не працює з усіма компіляторами, оскільки він покладається на невелику зміну реалізації std :: allocate_shared. Але ця зміна тепер є запропонованим оновленням для стандартних бібліотек, тому в майбутньому його можуть підтримати всі компілятори. Він працює на GCC 4.7.

Запит на зміну стандартної робочої групи бібліотеки C ++ тут: http://lwg.github.com/isissue/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;
}

6

В ідеалі, я думаю, що ідеальне рішення вимагатиме доповнення до стандарту C ++. Ендрю Шеплер пропонує наступне:

(Перейдіть сюди для всієї теми)

ми можемо запозичити ідею у boost :: iterator_core_access. Я пропоную новий клас std::shared_ptr_accessбез публічних або захищених членів і вказати, що для std :: make_shared (args ...) і std :: alloc_shared (a, args ...), вирази :: new (pv) T (вперед (аргументи) ...) і 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.


1
Я думаю, що це гарне доповнення до стандарту, але мені недостатньо важливо витратити час, щоб приєднатися до групи Google і коментувати, а потім звернути увагу на цю групу та коментар. :-)
всезначний

4

Я усвідомлюю, що цей потік досить старий, але я знайшов відповідь, яка не потребує успадкування чи додаткових аргументів конструктору, що я не міг бачити в іншому місці. Це не портативно, хоча:

#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, можливо, він потребує налаштування на різних платформах.


1
Мене спокушає -1 це через відсутність портативності. Інші відповіді (особливо відповіді «ключового класу») є досить елегантними, а непереносний відповідь дуже потворним. Я не можу придумати причину, коли ти використовуєш непереносимий відповідь. Це не швидше чи щось подібне.
всезначний

@Omnifarious Це дійсно не портативний, і я б не рекомендував, але я вважаю, що це насправді семантично найбільш правильне рішення. У своїй відповіді я посилаюсь на пропозицію про доповнення std::shared_ptr_accessдо стандарту, яке можна вважати таким, що дозволяє робити вищезазначене простим та портативним способом.
Борис Дальштейн

3

Є більш волохата і цікава проблема, яка виникає, коли у вас є два суто пов'язані класи 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та робить 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);
  }
};

3

Якщо ви також хочете включити конвектор, який бере аргументи, це може трохи допомогти.

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

3

[Редагувати] Я читав нитку, зазначену вище, у стандартизованій 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 бути приватними, а також використовувати кінцевий специфікатор.

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

Нарешті, хтось сказав, що Кланг скаржився на те, що Factory :: Type є приватним, коли його використовують як друга, тож просто опублікуйте його, якщо це так. Викриття цього не приносить шкоди.


3

У мене була така ж проблема, але жоден із існуючих відповідей був справді задовільним, оскільки мені потрібно передати аргументи захищеному конструктору. Більше того, мені потрібно зробити це протягом декількох класів, кожен з яких бере різні аргументи.

Для цього і, спираючись на кілька існуючих відповідей, на які всі використовують подібні методи, я представляю цей маленький самородок:

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

1

Корінь проблеми полягає в тому, що якщо ваша знайома функція або клас робить виклики нижчого рівня до вашого конструктора, вони також повинні бути закріплені за рамками. 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 років, я заплатив велику ціну за час, біль і побічні ефекти, щоб виправити це, коли це було зроблено неправильно років тому.


2
std::_Ref_count_objє деталізацією реалізації. Це означає, що зараз це рішення може працювати для вас на вашій платформі. Але це може не працювати для інших і може перестати працювати будь-коли, коли ваш компілятор оновлюється, а може, навіть якщо ви просто змінюєте прапорці компіляції.
Франсуа Андріо

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

1
Не використовує std::make_shared.
Брайан

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

Це просто дублікат цієї відповіді: stackoverflow.com/a/27832765/167958
всілякий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.