Как мне вызвать :: 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 ]

2 голосов
/ 05 января 2017

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

#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");
}
2 голосов
/ 27 ноября 2013

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

Скажи, что А - это «мастер-класс», а В - «раб». Если вы хотите ограничить создание экземпляра 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);
  }
};
0 голосов
/ 20 июня 2019

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

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

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 )... );
}
0 голосов
/ 31 июля 2018

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

0 голосов
/ 07 сентября 2016
#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;
}
0 голосов
/ 09 октября 2015

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

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