Как вызвать:std:: make shared в классе только с защищенными или частными конструкторами?


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

testmakeshared.cpp

#include <memory>

class A {
 public:
   static ::std::shared_ptr<A> create() {
      return ::std::make_shared<A>();
   }

 protected:
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

но я получаю эту ошибку при компиляции:

g++ -std=c++0x -march=native -mtune=native -O3 -Wall testmakeshared.cpp
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:52:0,
                 from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/memory:86,
                 from testmakeshared.cpp:1:
testmakeshared.cpp: In constructor ‘std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc) [with _Tp = A, _Alloc = std::allocator<A>, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’:
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:518:8:   instantiated from ‘std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:986:35:   instantiated from ‘std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:313:64:   instantiated from ‘std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:531:39:   instantiated from ‘std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:547:42:   instantiated from ‘std::shared_ptr<_Tp1> std::make_shared(_Args&& ...) [with _Tp = A, _Args = {}]’
testmakeshared.cpp:6:40:   instantiated from here
testmakeshared.cpp:10:8: error: ‘A::A()’ is protected
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:400:2: error: within this context

Compilation exited abnormally with code 1 at Tue Nov 15 07:32:58

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

но я действительно хочу использовать как ::std::make_shared и предотвратить кого-либо от создания объекта этот класс, на который не указывает a ::std::shared_ptr. Есть ли способ сделать это?

14 131

14 ответов:

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

#include <memory>
#include <string>

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template <typename... T>
   static ::std::shared_ptr<A> create(T &&...args) {
      return ::std::make_shared<A>(this_is_private{0},
                                   ::std::forward<T>(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

::std::shared_ptr<A> bar()
{
   return A::create("George", 5);
}

::std::shared_ptr<A> errors()
{
   ::std::shared_ptr<A> retval;

   // Each of these assignments to retval properly generates errors.
   retval = A::create("George");
   retval = new A(A::this_is_private{0});
   return ::std::move(retval);
}

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

глядя на требования к std::make_shared в 20.7.2.2.6 shared_ptr creation [util.smartptr.общий.создать], пункт 1:

требуется: выражение ::new (pv) T(std::forward<Args>(args)...), где pv типа void* и указывает на хранилище, подходящее для хранения объекта типа T, должны быть хорошо сформированы. A должен быть распределителем (17.6.3.5). Конструктор копирования и деструктор A не должны бросать исключения.

С требование безоговорочно указано с точки зрения этого выражения, и такие вещи, как сфера охвата, не учитываются, я думаю, что такие трюки, как дружба, правы.

простое решение состоит в том, чтобы вывести из A. Для этого не нужно делать A интерфейс или даже полиморфный тип.

// interface in header
std::shared_ptr<A> make_a();

// implementation in source
namespace {

struct concrete_A: public A {};

} // namespace

std::shared_ptr<A>
make_a()
{
    return std::make_shared<concrete_A>();
}

возможно, самое простое решение. На основе предыдущего ответ Мохит Арон и включение предложения dlf.

#include <memory>

class A
{
public:
    static std::shared_ptr<A> create()
    {
        struct make_shared_enabler : public A {};

        return std::make_shared<make_shared_enabler>();
    }

private:
    A() {}  
};

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

#include <memory>

class A {
   public:
     static shared_ptr<A> Create();

   private:
     A() {}

     struct MakeSharedEnabler;   
 };

struct A::MakeSharedEnabler : public A {
    MakeSharedEnabler() : A() {
    }
};

shared_ptr<A> A::Create() {
    return make_shared<MakeSharedEnabler>();
}
struct A {
public:
  template<typename ...Arg> std::shared_ptr<A> static create(Arg&&...arg) {
    struct EnableMakeShared : public A {
      EnableMakeShared(Arg&&...arg) :A(std::forward<Arg>(arg)...) {}
    };
    return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
  }
  void dump() const {
    std::cout << a_ << std::endl;
  }
private:
  A(int a) : a_(a) {}
  A(int i, int j) : a_(i + j) {}
  A(std::string const& a) : a_(a.size()) {}
  int a_;
};

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

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

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

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

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

запрос на изменение рабочей группы стандартной библиотеки C++ находится здесь: http://lwg.github.com/issues/lwg-active.html#2070

патч GCC с примером использования здесь: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

решение работает над идеей использовать std::allocate_shared (вместо std:: make_shared) с пользовательским распределителем, который объявлен другом классу с помощью частного конструктора.

пример из ОП будет выглядеть так:

#include <memory>

template<typename Private>
struct MyAlloc : std::allocator<Private>
{
    void construct(void* p) { ::new(p) Private(); }
};

class A {
    public:
        static ::std::shared_ptr<A> create() {
            return ::std::allocate_shared<A>(MyAlloc<A>());
        }

    protected:
        A() {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;

        friend struct MyAlloc<A>;
};

int main() {
    auto p = A::create();
    return 0;
}

более сложный пример, основанный на утилите, над которой я работаю. С этим я не мог использовать Решение люка. Но тот, что был написан Омнифарием, мог быть адаптирован. Не то, чтобы в предыдущем примере каждый мог создать объект A с помощью MyAlloc, в этом нет способа создать A или B помимо метода create ().

#include <memory>

template<typename T>
class safe_enable_shared_from_this : public std::enable_shared_from_this<T>
{
    public:
    template<typename... _Args>
        static ::std::shared_ptr<T> create(_Args&&... p_args) {
            return ::std::allocate_shared<T>(Alloc(), std::forward<_Args>(p_args)...);
        }

    protected:
    struct Alloc : std::allocator<T>
    {  
        template<typename _Up, typename... _Args>
        void construct(_Up* __p, _Args&&... __args)
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    };
    safe_enable_shared_from_this(const safe_enable_shared_from_this&) = delete;
    safe_enable_shared_from_this& operator=(const safe_enable_shared_from_this&) = delete;
};

class A : public safe_enable_shared_from_this<A> {
    private:
        A() {}
        friend struct safe_enable_shared_from_this<A>::Alloc;
};

class B : public safe_enable_shared_from_this<B> {
    private:
        B(int v) {}
        friend struct safe_enable_shared_from_this<B>::Alloc;
};

int main() {
    auto a = A::create();
    auto b = B::create(5);
    return 0;
}

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

#include <memory>

#if defined(__cplusplus) && __cplusplus >= 201103L
#define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator<test>::construct<test>(test*);
#elif defined(_WIN32) || defined(WIN32)
#if defined(_MSC_VER) && _MSC_VER >= 1800
#define ALLOW_MAKE_SHARED(x) friend class std::_Ref_count_obj;
#else
#error msc version does not suport c++11
#endif
#else
#error implement for platform
#endif

class test {
    test() {}
    ALLOW_MAKE_SHARED(test);
public:
    static std::shared_ptr<test> create() { return std::make_shared<test>(); }

};
int main() {
    std::shared_ptr<test> t(test::create());
}

Я тестировал на windows и linux, возможно, потребуется настройка для разных платформ.

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

#include <memory>
#include <utility>

template<typename S>
struct enable_make : public S
{
    template<typename... T>
    enable_make(T&&... t)
        : S(std::forward<T>(t)...)
    {
    }
};

class foo
{
public:
    static std::unique_ptr<foo> create(std::unique_ptr<int> u, char const* s)
    {
        return std::make_unique<enable_make<foo>>(std::move(u), s);
    }
protected:
    foo(std::unique_ptr<int> u, char const* s)
    {
    }
};

void test()
{
    auto fp = foo::create(std::make_unique<int>(3), "asdf");
}

В идеале, я думаю, идеальное решение потребует дополнений к стандарту C++. Эндрю Шеплер предлагает следующее:

(Go здесь для всего потока)

мы можем позаимствовать идею из boost:: iterator_core_access. Я предлагаю новый класс std::shared_ptr_access без государственного или защищенные члены, и указать, что для std:: make_shared(args...) и std:: alloc_shared(a, args...), этот выражения:: new(pv) T(forward (args)...) и ptr - > ~T () должно быть хорошо сформированный в контексте std:: shared_ptr_access.

реализация std:: shared_ptr_access может выглядеть так:

namespace std {
    class shared_ptr_access
    {
        template <typename _T, typename ... _Args>
        static _T* __construct(void* __pv, _Args&& ... __args)
        { return ::new(__pv) _T(forward<_Args>(__args)...); }

        template <typename _T>
        static void __destroy(_T* __ptr) { __ptr->~_T(); }

        template <typename _T, typename _A>
        friend class __shared_ptr_storage;
    };
}

использование

Если / когда вышеуказанное будет добавлено к стандарту, мы просто сделаем:

class A {
public:
   static std::shared_ptr<A> create() {
      return std::make_shared<A>();
   }

 protected:
   friend class std::shared_ptr_access;
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

Если это также звучит как важное дополнение к стандарту для вас, не стесняйтесь добавлять свои 2 цента в связанную группу isocpp Google.

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

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

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

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

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

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

class B
{
public:
  class Pass
  {
    Pass() {}
    friend class A;
  };

  B(Pass, int someArgument)
  {
  }
};

class A
{
public:
  A()
  {
    // This is valid
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

class C
{
public:
  C()
  {
    // This is not
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

корень проблемы заключается в том, что если функция или класс, который вы друг, делает вызовы более низкого уровня для вашего конструктора, они тоже должны быть связаны. std:: make_shared-это не функция, которая на самом деле вызывает ваш конструктор, поэтому friending не имеет никакого значения.

class A;
typedef std::shared_ptr<A> APtr;
class A
{
    template<class T>
    friend class std::_Ref_count_obj;
public:
    APtr create()
    {
        return std::make_shared<A>();
    }
private:
    A()
    {}
};

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

#define SHARED_PTR_DECL(T) \
class T; \
typedef std::shared_ptr<T> ##T##Ptr;

#define FRIEND_STD_MAKE_SHARED \
template<class T> \
friend class std::_Ref_count_obj;

тогда ваше объявление класса выглядит довольно простой. Вы можете сделать один макрос для объявления ptr и класса, если вы предпочитаете.

SHARED_PTR_DECL(B);
class B
{
    FRIEND_STD_MAKE_SHARED
public:
    BPtr create()
    {
        return std::make_shared<B>();
    }
private:
    B()
    {}
};

это на самом деле важный вопрос. Чтобы сделать поддерживаемый, переносимый код, вам нужно скрыть как можно больше реализации.

typedef std::shared_ptr<A> APtr;

скрывает, как вы обрабатываете свой умный указатель немного, вы должны быть уверены, чтобы использовать свой typedef. Но если вы всегда должны создавать его с помощью make_shared, это побеждает цель.

приведенный выше пример заставляет код использовать ваш класс для использования конструктора смарт-указателя, что означает, что если вы переключитесь на новый вкус смарт-указателя, вы измените объявление класса, и у вас есть приличные шансы на завершение. Не предполагайте, что ваш следующий босс или проект будет использовать stl, boost и т. д. планируй изменить его когда-нибудь.

делая это в течение почти 30 лет, я заплатил большую цену во времени, боли и побочных эффектах, чтобы исправить это, когда это было сделано неправильно много лет назад.

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

class CVal
{
    friend std::shared_ptr<CVal>;
    friend std::_Ref_count<CVal>;
public:
    static shared_ptr<CVal> create()
    {
        shared_ptr<CVal> ret_sCVal(new CVal());
        return ret_sCVal;
    }

protected:
    CVal() {};
    ~CVal() {};
};
#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
private:
    A(){}
    explicit A(int a):m_a(a){}
public:
    template <typename... Args>
    static std::shared_ptr<A> create(Args &&... args)
    {
        class make_shared_enabler : public A
        {
        public:
            make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...){}
        };
        return std::make_shared<make_shared_enabler>(std::forward<Args>(args)...);
    }

    int val() const
    {
        return m_a;
    }
private:
    int m_a=0;
};

int main(int, char **)
{
    std::shared_ptr<A> a0=A::create();
    std::shared_ptr<A> a1=A::create(10);
    std::cout << a0->val() << " " << a1->val() << std::endl;
    return 0;
}