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_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 crie um objeto dessa classe que não seja apontado por a ::std::shared_ptr
. Existe alguma maneira de conseguir isso?
c++
c++11
shared-ptr
Omniforme
fonte
fonte
Respostas:
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:
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.
fonte
protected
vez deprivate
. E por "it", estou me referindo àthis_is_private
classe, que talvez deva ser renomeada nesse caso. Eu costumo chamá-loconstructor_access
no meu código.{}
para a marca particular sem ter acesso ao nome do tipo (testado com g ++ 4.9.0). Sem parâmetros reais, ele tenta construir aA
partir 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.this_is_private
um ctor particular, poderá fazer da classe A um amigo. Parece fechar a brecha.Examinando os requisitos
std::make_shared
da 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], parágrafo 1: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 deA
uma interface ou mesmo de um tipo polimórfico.fonte
shared_ptr
armazena um deleter no momento da instanciação e, se você estiver usandomake_shared
o deleter, absolutamente deve estar usando o tipo certo.Possivelmente a solução mais simples. Baseado na resposta anterior de Mohit Aron e incorporando a sugestão de dlf.
fonte
A
tem 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 privadosA
visíveis comomake_shared_enabler
construtores. Usar o recurso de herança de construtores (using A::A;
) parece não ajudar aqui, porque os construtores ainda serão privados.class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }
. Veja aqui cpp.sh/65qbr .Aqui está uma solução elegante para isso:
fonte
MakeSharedEnabler
localmente dentroA::Create()
.Que tal agora?
fonte
::std::make_shared
tem 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
.fonte
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:
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 ().
fonte
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)
Uso
Se / quando o acima for adicionado ao padrão, faríamos simplesmente:
Se isso também lhe parece uma adição importante ao padrão, adicione dois centavos ao Grupo isocpp vinculado do Google.
fonte
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:
Eu testei no Windows e Linux, pode precisar de ajustes para diferentes plataformas.
fonte
std::shared_ptr_access
ao padrão, que pode ser vista como permitindo fazer o que foi dito acima de maneira simples e portátil.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.
Infelizmente, chamar
std::make_shared<B>()
de um método deA
fará com que o compilador se queixe deB::B()
ser privado.Minha solução para isso é criar uma
Pass
classe fictícia pública (assim comonullptr_t
) dentroB
que tenha construtor privado e seja amigaA
e torneB
público o construtor de s e adicionePass
aos seus argumentos, assim.fonte
Se você também deseja habilitar um construtor que aceita argumentos, isso pode ajudar um pouco.
fonte
[Editar] Li o tópico mencionado acima em uma
std::shared_ptr_access<>
proposta padronizada . Dentro havia uma resposta observando uma correçãostd::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. Funcionastd::enable_shared_from_this<>
também, portanto, obviamente, seria preferível à minha solução original nesta resposta. Aqui está...[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.
Observe que a abordagem acima não funciona bem
std::enable_shared_from_this<>
porque a inicialstd::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 ...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.
fonte
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:
fonte
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.
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
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.
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.
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.
fonte
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.Você pode usar isto:
fonte
std::make_shared
.fonte