Как я могу вызвать:: 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 и не позволять кому-либо создавать объект этого класса, на который не указывает ::std::shared_ptr. Есть ли способ сделать это?

Ответ 1

Этот ответ, вероятно, лучше, и тот, который я, скорее всего, приму. Но я также придумал метод, который уродливее, но все еще позволяет все еще быть встроенным и не требует производного класса:

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

Ответ 2

Глядя на требования к std::make_shared в 20.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>();
}

Ответ 3

Возможно, самое простое решение. На основе предыдущего ответа Мохита Арона и включения предложения 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() {}  
};

Ответ 4

Здесь аккуратное решение для этого:

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Это не придумано мной, но это идея Джонатана Вакли (разработчика GCC).

К сожалению, он не работает со всеми компиляторами, потому что он полагается на небольшое изменение в реализации std:: allocate_shared. Но это изменение теперь является предлагаемым обновлением для стандартных библиотек, поэтому в будущем оно может поддерживаться всеми компиляторами. Он работает на GCC 4.7.

Запрос на изменение рабочей группы библиотеки библиотеки С++ здесь: 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;
}

Ответ 8

Я понимаю, что этот поток довольно старый, но я нашел ответ, который не требует наследования или дополнительных аргументов конструктору, который я не видел в другом месте. Однако он не переносится:

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

Ответ 9

В идеале, я думаю, что идеальное решение потребует дополнений к стандарту 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.

Ответ 10

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

Скажите, что A - это "мастер-класс", а B - "подчиненный". Если вы хотите ограничить экземпляр B только A, вы должны сделать конструктор B частным, а friend B - A, подобный этому

class B
{
public:
    // B your methods...

private:
    B();
    friend class A;
};

К сожалению, вызов std::make_shared<B>() из метода A заставит компилятор жалобы на B::B() быть закрытым.

Моим решением для этого является создание общедоступного Pass dummy-класса (точно так же, как nullptr_t) внутри B, который имеет частный конструктор и является другом с A и создает конструктор B public и добавляет 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);
  }
};

Ответ 11

Если вы также хотите включить constuctor, который принимает аргументы, это может немного помочь.

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

Ответ 12

[Редактировать] Я прочитал вышеупомянутую std::shared_ptr_access<> стандартного предложения std::shared_ptr_access<>. Внутри был ответ, отмечающий исправление std::allocate_shared<> и пример его использования. Я адаптировал его к заводскому шаблону ниже и протестировал его в gcc С++ 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 является приватным при использовании в качестве друга, поэтому просто сделайте его публичным, если это так. Разоблачение это не вредит.

Ответ 13

Я придумал такой шаблон, который отлично справляется с проблемой защищенных конструкторов:

// helper template not used directly in code:
template<typename T>
struct CHelpExposeProtectedConstructor : public T
{
    template<typename...Args>
    CHelpExposeProtectedConstructor(Args...args)
        : T(std::forward<Args>(args)...)
    {}
};

/**
 * this uses std::make_shared on classes which have protected constructors
 */
template<typename T, typename...Args>
auto MakeSharedFromProtected(Args...args) -> std::shared_ptr<T>
{
    return std::make_shared<CHelpExposeProtectedConstructor<T>>(std::forward<Args>(args)...);
}

Вместо использования MakeSharedFromProtected вместо make_shared и решить проблему.

К сожалению, это не помогает в случае частных конструкторов.

Ответ 14

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

Ответ 15

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

В связи с этим, основываясь на нескольких существующих ответах, в которых все используют похожие методы, я представляю этот маленький самородок:

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

Ответ 16

Вы можете использовать это:

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

Ответ 17

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