c++ - make_shared - std shared_ptr example




Como faço para chamar:: std:: make_shared em uma classe com apenas construtores protegidos ou privados? (10)

A raiz do problema é que, se a função ou classe you friend fizer chamadas de nível inferior para seu construtor, elas também precisam ser amigas. std :: make_shared não é a função que está realmente chamando seu construtor de forma que não faz diferença.

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 está realmente chamando seu construtor, então ele precisa ser um amigo. Como isso é um pouco obscuro, eu uso uma macro

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

Então sua declaração de classe parece bastante simples. Você pode criar uma única macro para declarar o ptr e a classe, se preferir.

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

Esta é realmente uma questão importante. Para fazer um código portátil e sustentável, você precisa ocultar o máximo possível da implementação.

typedef std::shared_ptr<A> APtr;

Esconde como você está lidando com seu ponteiro inteligente um pouco, você tem que ter certeza de usar seu typedef. Mas se você sempre tem que criar um usando o make_shared, ele acaba com o propósito.

O exemplo acima força o código usando sua classe para usar seu construtor de ponteiro inteligente, o que significa que se você mudar para um novo tipo de ponteiro inteligente, você mudará sua declaração de classe e terá uma boa chance de terminar. NÃO presuma que seu próximo chefe ou projeto usará stl, boost etc. planeje mudá-lo um dia.

Fazendo isso por quase 30 anos, eu paguei um grande preço no tempo, dor e efeitos colaterais para reparar isso quando foi feito errado anos atrás.

Eu tenho esse código que não funciona, mas acho que a intenção é clara:

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

Mas eu recebo esse erro quando eu compilo:

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

Esta mensagem é basicamente dizendo que algum método aleatório na pilha de instanciação de template de ::std::make_shared não pode acessar o construtor porque está protegido.

Mas eu realmente quero usar os dois ::std::make_shared e impedir que alguém faça um objeto desta classe que não seja apontada por um ::std::shared_ptr . Existe alguma maneira de conseguir isso?


Aqui está uma solução legal para isso:

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

Eu percebo que este segmento é bastante antigo, mas eu encontrei uma resposta que não requer herança ou argumentos extras para o construtor que eu não podia ver em outro lugar. Não é portátil embora:

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

Eu testei no windows e linux, pode precisar de ajustes para diferentes plataformas.


Há um problema mais cabeludo e interessante que acontece quando você tem duas classes estritamente relacionadas A e B que funcionam juntas.

Say A é a "master class" e B é "slave". Se você quiser restringir a instanciação de B apenas para A, você tornaria o construtor de B privado e o amigo B para A assim

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

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

Infelizmente, chamar std::make_shared<B>() de um método A fará com que o compilador se queixe de B::B() sendo privado.

Minha solução para isso é criar uma classe dummy public Pass (assim como nullptr_t ) dentro de B que tenha um construtor privado e seja amigo de A e torne público o construtor de B e adicione Pass aos seus argumentos, assim.

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

Observando os requisitos de std::make_shared em 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], parágrafo 1:

Requer: A expressão ::new (pv) T(std::forward<Args>(args)...) , onde pv tem tipo void* e aponta para armazenamento adequado para conter um objeto do tipo T , deve ser bem formado . A deve ser um alocador (17.6.3.5). O construtor de cópia e o destruidor de A não devem lançar exceções.

Como o requisito é incondicionalmente especificado em termos dessa expressão e coisas como o escopo não são levadas em conta, acho que truques como amizade estão corretos.

Solução simples é derivar de. Isso não precisa exigir uma interface A ou até mesmo um tipo polimórfico.

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

Possivelmente a solução mais simples. Baseado na answer anterior de Mohit Aron e incorporando a sugestão do 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() {}  
};

Se você também quiser habilitar um constuctor que aceita argumentos, isso pode ajudar um pouco.

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

Você pode usar isto:

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

Essa resposta provavelmente é melhor e provavelmente eu aceito. Mas também criei um método que é mais feio, mas ainda deixa tudo ainda em linha e não requer uma classe derivada:

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

Edit 2017-01-06: Eu mudei isso para deixar claro que esta ideia é clara e simplesmente extensível para construtores que levam argumentos porque outras pessoas estavam fornecendo respostas nesse sentido e pareciam confusas sobre isso.


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