Como chamo :: std :: make_shared em uma classe com apenas construtores protegidos ou privados?


187

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 o 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

Essa mensagem está basicamente dizendo que algum método aleatório localizado na pilha de instanciação de modelo ::std::make_sharednão pode acessar o construtor porque está protegido.

Mas eu realmente quero usar os dois ::std::make_sharede impedir que alguém crie um objeto dessa classe que não seja apontado por a ::std::shared_ptr. Existe alguma maneira de conseguir isso?


Você pode marcar a função no fundo que precisa do construtor como amigo, mas que não será portátil.
Dani

@Dani: Sim, seria bom ter uma solução portátil. Mas isso funcionaria.
omniforme

Respostas:


109

Essa resposta é provavelmente melhor e a que provavelmente aceitarei. Mas também inventei um método mais feio, mas ainda permite que tudo continue alinhado 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);
}

Editar 06-01-2017: Eu mudei isso para deixar claro que essa ideia é clara e simplesmente extensível a construtores que aceitam argumentos porque outras pessoas estavam fornecendo respostas nesse sentido e pareciam confusos sobre isso.


14
Na verdade, sou um grande fã dessas estruturas sem sentido usadas apenas como chaves . Eu prefiro isso à solução de Luc, mas isso pode ser o meu elo contra a herança.
Matthieu M.

2
Concordo, eu gosto disso também.
ildjarn

3
@Berkus: Então faça isso em protectedvez de private. E por "it", estou me referindo à this_is_privateclasse, que talvez deva ser renomeada nesse caso. Eu costumo chamá-lo constructor_accessno meu código.
dalle

1
Infelizmente, isso não funciona se o seu construtor usa parâmetros reais; nesse caso, você pode simplesmente passar {}para a marca particular sem ter acesso ao nome do tipo (testado com g ++ 4.9.0). Sem parâmetros reais, ele tenta construir a Apartir de {}, embora eu não tenha idéia do porquê e falhe. Acho que tornar o construtor this_is_private privado e fornecer um método estático para criá-lo o corrige, pois não deve haver maneira de acessar esse método de fora, a menos que você vaze o tipo em uma assinatura de função de membro.
Stefan

3
Stefan, se você der this_is_privateum ctor particular, poderá fazer da classe A um amigo. Parece fechar a brecha.
Steven Kramer

78

Examinando os requisitos std::make_sharedda 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 pvtem tipo void*e aponta para armazenamento adequado para armazenar um objeto do tipo T, deve ser bem formada. Adeve ser um alocador (17.6.3.5). O construtor e destruidor da cópia Anão deve lançar exceções.

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

Uma solução simples é derivar A. Isso não requer a criação de Auma interface ou mesmo de 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>();
}

1
Ah, essa é uma resposta muito inteligente, e possivelmente melhor do que outra que eu tinha pensado.
omniforme

Uma pergunta, no entanto, o shared_ptr não excluirá um A e não um concreto_A, e isso não pode causar problemas?
omniforme

8
Ahh, é porque shared_ptrarmazena um deleter no momento da instanciação e, se você estiver usando make_sharedo deleter, absolutamente deve estar usando o tipo certo.
omniforme

1
A pergunta @LucDanton não é sobre interfaces, pois o título sugere que ele também está pedindo um ctor particular. Além disso, é por isso que estou nessa questão de qualquer maneira. Algum código antigo com classes machiavelli, que possui um ctor particular e um método create retornando um ponteiro bruto, e estou tentando convertê-los em ponteiros inteligentes.
zahir

2
Eu gosto dessa abordagem (usando ela mesma), mas você precisa de um destruidor virtual. Ela se estende bem aos construtores com argumentos (apenas forneça um construtor de passagem). E se você estiver usando protegido em vez de privado, poderá torná-lo completamente invisível para os usuários do cabeçalho.
31517 Joe Steele

69

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

5
se Atem construtores não-padrão que você também vai precisar para expô-los: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Isso torna todos os construtores privados Avisíveis como make_shared_enablerconstrutores. Usar o recurso de herança de construtores ( using A::A;) parece não ajudar aqui, porque os construtores ainda serão privados.
anton_rh

2
@anton_rh: você não pode adicionar argumentos de modelo às classes internas. Veja aqui .
bobbel

3
Hm ... Parece que você está certo. No meu caso struct não era local, mas foi um struct particular: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Veja aqui cpp.sh/65qbr .
anton_rh

Isso funciona muito bem. Existe alguma chance de tornar essa propriedade herdável, para que esse padrão não precise ser repetido várias vezes? Particularmente a versão que expõe construtores não padrão seria muito interessante para mim. A versão padrão "meramente" exigiria alguma construção sintática que substitua A por qualquer que seja a classe que herda a classe. Eu não estou ciente de qualquer coisa assim, mas eu não ficaria surpreso ao saber que ela existe ...
Kjeld Schmidt

30

Aqui está uma solução elegante 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>();
}

3
Eu gosto disso. Isso pode ser um pouco mais simples, definindo MakeSharedEnablerlocalmente dentro A::Create().
dlf 29/07

Idéia impressionante Mohit isso me ajudou muito.
Jnana

12

Que tal agora?

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

13
Isso funciona muito bem. Mas ::std::make_sharedtem funcionalidade acima e além de simplesmente fazer um shared_ptr para alguma coisa. Ele aloca a contagem de referência junto com o objeto para que eles fiquem próximos um do outro. Eu realmente, realmente quero usar ::std::make_shared.
omniforme

O excluída assigment e copiar operadores proíbem este
Dani

7
Essa é realmente a abordagem mais direta, embora não seja exatamente o que a pergunta estava fazendo. O make_shared tem algumas características interessantes e eu tento usá-lo sempre que possível, mas nessa situação parece bastante provável que as vantagens de desempenho em tempo de execução do make_shared não superem a complexidade extra do código e a cerimônia realmente necessárias para usá-lo. Se você realmente precisa do desempenho do make_shared, enlouqueça, mas não negligencie a simplicidade de usar o construtor do shared_ptr.
Kevin

Tenha cuidado com vazamentos de memória embora ... ver esta questão stackoverflow.com/a/14837300/2149539
dgmz

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

Isso é basicamente a mesma coisa que a resposta de Luc Danton, embora transformá-la em uma classe local seja um toque agradável. Alguma explicação para acompanhar o código pode tornar essa uma resposta muito melhor.

Normalmente, quero escrever uma função tão pequena no arquivo de cabeçalho, mas não no arquivo cc. Em segundo lugar, na prática, eu uso uma macro que se parece com #define SharedPtrCreate (T) template <typename ... Arg> .....
alfa

Boa resposta. Eu até colocaria isso em uma macro chamada como IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

Como não gostei das respostas já fornecidas, decidi pesquisar e encontrei uma solução que não é tão genérica quanto as respostas anteriores, mas eu gosto mais (tm). Em retrospecto, não é muito melhor do que o fornecido por Omnifarius, mas pode haver outras pessoas que também gostam :)

Isso não foi inventado por mim, mas é a idéia de Jonathan Wakely (desenvolvedor do GCC).

Infelizmente, ele não funciona com todos os compiladores, porque depende de uma pequena alteração na implementação std :: alocate_shared. Mas essa alteração agora é uma atualização proposta para as bibliotecas padrão, portanto, pode ser suportada por todos os compiladores no futuro. Funciona no GCC 4.7.

A solicitação de alteração do Grupo de Trabalho da Biblioteca padrão C ++ está aqui: http://lwg.github.com/issues/lwg-active.html#2070

O patch do GCC com um exemplo de uso está aqui: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

A solução trabalha com a idéia de usar std :: assignate_shared (em vez de std :: make_shared) com um alocador personalizado que é declarado amigo da classe com o construtor privado.

O exemplo do OP ficaria assim:

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

Um exemplo mais complexo baseado no utilitário em que estou trabalhando. Com isso, não pude usar a solução de Luc. Mas o de Omnifarius poderia ser adaptado. Não que enquanto no exemplo anterior todos possam criar um objeto A usando o MyAlloc neste, não há como criar A ou B além do método 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;
}

6

Idealmente, acho que a solução perfeita exigiria adições ao padrão C ++. Andrew Schepler propõe o seguinte:

(Vá aqui para toda a discussão)

podemos emprestar uma idéia de boost :: iterator_core_access. Eu proponho uma nova aulastd::shared_ptr_access sem membros públicos ou protegidos e para especificar isso para std :: make_shared (args ...) e std :: assign_shared (a, args ...), as expressões :: new (pv) T (forward (args) ...) e ptr-> ~ T () devem ser bem formados no contexto de std :: shared_ptr_access.

Uma implementação de std :: shared_ptr_access pode parecer com:

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

Uso

Se / quando o acima for adicionado ao padrão, faríamos simplesmente:

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

Se isso também lhe parece uma adição importante ao padrão, adicione dois centavos ao Grupo isocpp vinculado do Google.


1
Eu acho que é uma boa adição ao padrão, mas não é importante o suficiente para eu dedicar um tempo ao grupo do Google e comentar e depois prestar atenção nesse grupo e no comentário. :-)
Onipotente 8/08/19

4

Sei que esse segmento é bastante antigo, mas encontrei uma resposta que não requer herança ou argumentos extras para o construtor que não consegui ver em nenhum outro lugar. No entanto, não é portátil:

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


1
Estou tentado a -1 por falta de portabilidade. As outras respostas (particularmente as respostas da "classe principal") são bastante elegantes e a resposta não portátil é muito feia. Não consigo pensar em uma razão para você usar a resposta não portátil. Não é mais rápido ou algo assim.
Onívoro

@ Omnifarious Na verdade, não é portátil e eu não recomendaria, mas acredito que essa seja, de fato, a solução semanticamente mais correta. Na minha resposta , vinculo a uma proposta de adição std::shared_ptr_accessao padrão, que pode ser vista como permitindo fazer o que foi dito acima de maneira simples e portátil.
Boris Dalstein

3

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

Diga A é a "classe principal" e B é o seu "escravo". Se você deseja restringir a instanciação de B apenas a A, tornaria o construtor de B privado e o amigo B a A assim.

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

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

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

Minha solução para isso é criar uma Passclasse fictícia pública (assim como nullptr_t) dentro Bque tenha construtor privado e seja amiga Ae torne Bpúblico o construtor de s e adicione Passaos 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);
  }
};

3

Se você também deseja habilitar um construtor 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");
}

3

[Editar] Li o tópico mencionado acima em uma std::shared_ptr_access<>proposta padronizada . Dentro havia uma resposta observando uma correção std::allocate_shared<>e um exemplo de seu uso. Eu o adaptei a um modelo de fábrica abaixo e o testei no gcc C ++ 11/14/17. Funciona std::enable_shared_from_this<>também, portanto, obviamente, seria preferível à minha solução original nesta resposta. Aqui está...

#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] Encontrei uma solução usando o construtor de aliasing de ponteiro compartilhado. Permite que o ctor e o dtor sejam privados, assim como o uso do especificador final.

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

Observe que a abordagem acima não funciona bem std::enable_shared_from_this<>porque a inicial std::shared_ptr<>é para o wrapper e não o tipo em si. Podemos resolver isso com uma classe equivalente compatível com a fábrica ...

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

Por fim, alguém disse que o clang reclamou sobre o Factory :: Type ser privado quando usado como amigo, então torne-o público, se for o caso. Expor isso não faz mal.


3

Eu tive o mesmo problema, mas nenhuma das respostas existentes foi realmente satisfatória, pois preciso passar argumentos para o construtor protegido. Além disso, preciso fazer isso em várias classes, cada uma tendo argumentos diferentes.

Para esse efeito, e com base em várias respostas existentes que usam métodos semelhantes, apresento esta pequena pepita:

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

1

A raiz do problema é que, se a função ou classe de seu amigo fizer chamadas de nível inferior ao seu construtor, elas também deverão ser amigas. std :: make_shared não é a função que está chamando seu construtor de forma tão amigável 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 criar código portátil e de manutenção, você precisa ocultar o máximo possível da implementação.

typedef std::shared_ptr<A> APtr;

oculta como você lida um pouco com o ponteiro inteligente, use o typedef. Mas se você sempre tiver que criar um usando make_shared, isso derrota o objetivo.

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

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


2
std::_Ref_count_objé um detalhe de implementação. Isso significa que, embora essa solução possa funcionar para você, por enquanto, em sua plataforma. Mas pode não funcionar para outras pessoas e pode parar de funcionar sempre que o compilador for atualizado ou talvez mesmo se você apenas alterar os sinalizadores de compilação.
François Andrieux

-3

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

1
Não usa std::make_shared.
Brian

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

Esta é apenas uma duplicata desta resposta: stackoverflow.com/a/27832765/167958
Omnifarious
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.