Как мне вызвать :: 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и другое, чтобы никто не смог создать объект этого класса, на который не указывает объект ::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);
}

Редактировать 2017-01-06: я изменил это, чтобы было ясно, что эта идея понятна и просто расширяема для конструкторов, которые принимают аргументы, потому что другие люди давали ответы в том же духе и, казалось, были смущены этим.

всевозможный
источник
14
На самом деле, я большой поклонник тех бессмысленных структур, которые используются только в качестве ключей . Я предпочитаю это решению Люка, но это может быть моим предубеждением против наследования.
Матье М.
2
Согласен, мне это тоже нравится больше.
ildjarn
3
@Berkus: Тогда сделай это protectedвместо private. И под «этим» я имею в виду this_is_privateкласс, который, возможно, следует переименовать в таком случае. Я обычно называю это constructor_accessв своем коде.
Далле
1
К сожалению, это не работает, если ваш конструктор принимает реальные параметры; в этом случае вы можете просто перейти {}к закрытому тегу, не имея доступа к имени типа (протестировано с g ++ 4.9.0). Без реальных параметров он пытается построить Aиз {}, хотя я понятия не имею, почему, и терпит неудачу. Я думаю, что сделав конструктор this_is_private закрытым и предоставив статический метод для его создания, это исправит его, так как не должно быть никакого доступа к этому методу извне, если вы не пропустите тип в сигнатуре функции-члена.
Стефан
3
Стефан, если ты отдашь this_is_privateприватный ctor, ты можешь сделать класс A другом. Кажется, чтобы закрыть лазейку.
Стивен Крамер
78

Рассматривая требования для std::make_shared20.7.2.2.6 создания shared_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
О, это очень умный ответ, и, возможно, лучше, чем другой, о котором я думал.
Всезнающий
Однако один вопрос: не удалит ли shared_ptr A, а не конкретный_A, и не может ли это вызвать проблемы?
Всезнающий
8
Ах, это потому, что shared_ptrво время создания экземпляра хранитель удаляется, и если вы используете make_sharedсредство удаления, оно обязательно должно использовать правильный тип.
Всезнающий
1
@LucDanton Вопрос не об интерфейсах, поскольку заголовок предполагает, что он также запрашивает частный ctor. Кроме того, поэтому я все равно на этот вопрос. Некоторый старый код, имеющий классы Макиавелли, который имеет частный ctor и метод create, возвращающий необработанный указатель, и я пытаюсь преобразовать их в умные указатели.
Захир
2
Мне нравится этот подход (использующий его сам), но вам нужен виртуальный деструктор. Он хорошо распространяется на конструкторы с аргументами (просто предоставьте сквозной конструктор). И если вы используете защищенный, а не частный, вы можете сделать его полностью невидимым для пользователей заголовка.
Джо Стил
69

Возможно, самое простое решение. Основано на предыдущем ответе Мохит Арон и включает предложение ДЛФ.

#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 тем, какой класс наследует класс. Я не знаю ничего подобного, но я не удивлюсь, узнав, что оно существует ...
Кьельд Шмидт
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
Отличная идея, Мохит, она мне очень помогла.
Джнана
12

Как насчет этого?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
Шон
источник
13
Это прекрасно работает. Но ::std::make_sharedимеет функциональность сверх того, что просто делает shared_ptr чем-то. Он распределяет счетчик ссылок вместе с объектом, чтобы они находились близко друг к другу. Я действительно очень хочу использовать ::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) template <typename ... Arg> .....
альфа
Хороший ответ. Я бы даже поместил это в макрос, называемый как IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr
8

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

Это не придумано мной, но это идея Джонатана Уэйкли (разработчика 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 может быть адаптирован. Не то чтобы хотя в предыдущем примере каждый мог создать объект 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 (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.

Борис Дальштейн
источник
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, может потребоваться настройка для разных платформ.

ashleysmithgpu
источник
1
Я испытываю желание -1 это из-за отсутствия переносимости. Другие ответы (в частности, ответы «ключевого класса») довольно элегантны, а непереносимые ответы очень некрасивы. Я не могу придумать причину, по которой вы бы использовали непереносимый ответ. Это не быстрее или что-то в этом роде.
Всезнающий
@ Omnifarious Это действительно непереносимо, и я бы не советовал, но я считаю, что это на самом деле семантически наиболее правильное решение. В своем ответе я ссылаюсь на предложение о добавлении std::shared_ptr_accessв стандарт, которое можно рассматривать как позволяющее сделать все вышеперечисленное простым и переносимым способом.
Борис Дальштейн
3

Существует более сложная и интересная проблема, которая возникает, когда у вас есть два строго связанных класса A и B, которые работают вместе.

Скажи «А» - «мастер-класс», а «Б» - «Раб». Если вы хотите ограничить создание экземпляра B только для A, вы сделаете конструктор B личным, а друга 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);
  }
};
keebus
источник
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";
}

Наконец, кто-то сказал, что clang жаловался на то, что Factory :: Type является приватным при использовании в качестве друга, поэтому просто сделайте его публичным, если это так. Разоблачение это не вредит.

user1715587
источник
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 лет, я заплатил большую цену во времени, боли и побочных эффектах, чтобы исправить это, когда это было сделано неправильно много лет назад.

brtip
источник
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;
}
spise
источник
Это всего лишь дубликат этого ответа: stackoverflow.com/a/27832765/167958
Всезнающий