Как мне вызвать :: std :: make_shared в классе только с защищенными или закрытыми конструкторами? - PullRequest
150 голосов
/ 16 ноября 2011

У меня есть этот код, который не работает, но я думаю, что цель ясна:

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.Есть ли способ сделать это?

Ответы [ 16 ]

100 голосов
/ 16 ноября 2011

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

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

69 голосов
/ 16 ноября 2011

Рассмотрение требований для 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>();
}
59 голосов
/ 01 августа 2014

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

Вот изящное решение для этого:

#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>();
}
11 голосов
/ 16 ноября 2011

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

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
10 голосов
/ 08 января 2015
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_;
};
8 голосов
/ 05 июля 2012

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

Это не придумано мной, но это идея Джонатана Уэйкли (разработчика 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;
}
4 голосов
/ 10 августа 2015

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

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

3 голосов
/ 10 июля 2018

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

2 голосов
/ 18 ноября 2018

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

...