Caros,
Estou com uma dúvida que meu Google-FU não consegue resolver. Como faço para criar uma função que em um dos parâmetros ela aceite um ponteiro que seja unique_ptr ou shared_ptr? Não que um template que aceite os dois, nem queria ter que fazer overloads para cada um:
void myFunc( std::any_ptr<MyClass> );
int main()
{
myFunc( std::shared_ptr<MyClass>( new MyClass() ));
myFunc( std::unique_ptr<MyClass>( new MyClass() ));
}
Alguém sabe?
Tks.
Então, o caso de uso é uma API que não conhece o resto do programa, logo não consigo determinar neste ponto se devo usar shared ou unique. Outra coisa, é que a API é task-based, e dependendo do caso, ela pode executar imediatamente (cache-hit) ou agendar algo p/ ser executado mais tarde e uma thread ainda não determinada (cache-miss); logo, eu não posso usar referência.
--
Antes de enviar um e-mail para o grupo leia:
http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
--~--~---------~--~----~---------------------------------~----------~--~----~
[&] Colabore com a Pesquisa de Preferência de Conteúdo
para Eventos do Grupo C & C++ Brasil:
http://www.surveymonkey.com/s/GBBGTXN
------~----~-------~---~---~---~---~----------------~------------~---------~
[&] C & C++ Brasil - http://www.ccppbrasil.org/
Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
Para mais opções, visite http://groups.google.com/group/ccppbrasil
--~--~---------~--~----~--~-~--~---~----~-----------------~--~----------~
Emprego & carreira: vag...@ccppbrasil.org
http://groups.google.com/group/dev-guys?hl=en
Por favor não faça isso. Você não está transferindo ownership do
objeto em unique_ptr
e nem mantendo ownership compartilhado com shared_ptr. O que é
bastante contra-intuitivo.
--
Felipe Magno de Almeida
O argumento é IN, obrigatório e não-null. O objeto não vai ser transferido. Não sei dizer se vai ser compartilhado ou não. O tempo de vida dele é indeterminado (ele pode morrer ao final da função ou não). Mas fique tranquillo. Eu já olhei bem a especificação, pensei muito na arquitetura, e pensei que se houver como fazer isso, seria a solução mais simples. A que tenho, é sempre converter o unique em shared. Não queria ter que fazer isso.
Eu pensei em algo assim, que é na linha da gambiarra mesmo:
template<class T> class any_ptr
{
protected:
std::shared_ptr<T> m_shared;
std::unique_ptr<T> m_unique;
public:
any_ptr( std::shared_ptr<T> shared ): m_shared( shared ) {};
any_ptr( std::unique_ptr<T> unique): m_unique( unique ) {};
T* operator->() const { if ( m_shared ) return m_shared.get(); else return m_unique.get(); }
T* get() const { if ( m_shared ) return m_shared.get(); else return m_unique.get(); }
};
class MyClass
{
public:
MyClass(int i): x(i) {}
int x;
};
void myfunc( any_ptr<MyClass> myObj )
{
std::cout << myObj->x << "\n";
}
int main(int argc, char **argv)
{
std::shared_ptr<MyClass> shared( new MyClass(4) );
std::unique_ptr<MyClass> unique( new MyClass(2) );
myfunc( shared );
myfunc( unique );
return 0;
}
Na minha opini�o, a melhor maneira � criar uma classe base (interface):
class MyClassHolder
{
public:
virtual ~MyClassHolder() {}
virtual MyClass* get() = 0;
};
E um template MyClassHolderImpl que aceite os dois tipos de smart pointer.
As classes da sua biblioteca armazenar�o uma MyClassHolder com o smart
pointer necess�rio ao funcionamento do pr�prio c�digo. Quem vai se
encarregar de lidar com o smart pointer do chamador ser� a
MyClassHolderImpl.
Adriano
Não acho tão ruim não. Provavelmente melhor que duplicar o código
binário (template) todo para shared_ptr/unique_ptr.
Qualquer outra alternativa de type-erase será tão eficiente ou pior
que shared_ptr.
> --
Peraí, a função não obtém ownership (compartilhado ou não) do objeto?
Se essa função pode ser executada posteriormente é bom tomar cuidado
como o seu usuário poderia fazer usso dessa função, ter que lidar com
tempo de vida manualmente de objetos junto com implementation
details de quando/como/onde essa função é escalonada etc é bem complicado.
template<class T> class any_ptr
{
protected:
std::shared_ptr<T> m_shared;
std::unique_ptr<T> m_unique;
public:
any_ptr( std::shared_ptr<T> shared ): m_shared( shared ) {};
any_ptr( std::unique_ptr<T>&& unique): m_unique( std::move( unique )) {};
T* operator->() const { if ( m_shared ) return m_shared.get(); else return m_unique.get(); }
T* get() const { if ( m_shared ) return m_shared.get(); else return m_unique.get(); }
};
class MyClass
{
public:
MyClass(int i): x(i) {}
int x;
};
void myfunc( const any_ptr<MyClass>& myObj )
{
std::cout << myObj->x << "\n";
}
int main(int argc, char **argv)
{
std::shared_ptr<MyClass> shared( new MyClass(4) );
std::unique_ptr<MyClass> unique( new MyClass(2) );
myfunc( shared );
myfunc( std::move( unique ));
return 0;
}
>void myfunc( const any_ptr<MyClass>& myObj )
> {
> std::cout << myObj->x << "\n";
> }
Você tem um exemplo mais parecido com a realidade?
Você tem uma condição dinamica de transferencia?
seria algo assim?
void myfunc( const any_ptr<MyClass>& myObj )
{
if (condicao1)
{
//vai ser transferido para um share_ptr
}
else if (condicao2)
{
//vai ser transferido para um unique_ptr
}
else if (condicao3)
{
// nao vai acontecer nada com myobjt
}
else
{
//vai ser deletado aqui
}
}
Não. Nada do tipo. Tenho uma fila de tarefas. Cada tarefa é um pedido que pode ser um 'get' ou um 'put'. No caso do 'get', eu posso verificar se já tenho o dado local (neste caso retorna imediatamente) ou se tenho que agendar para pegar via I/O (vai rodar numa thread indeterminada). Os 'gets' compartilham o objeto, pois lá será preenchido o valor que será usado. O put é sempre assíncrono, e não compartilha o objeto.
Todo o código disso está espalhado por vários arquivos, e o ponto onde preciso do any_ptr é a central de gerenciamento de tarefas; que não conhece mais ninguém, por isso não sabe dizer nem se o objeto é um 'get' ou 'put'.
>Cada tarefa é um pedido que pode ser um 'get' ou um 'put'.
>No caso do 'get', eu posso verificar se já tenho o dado local (neste caso retorna imediatamente) ou se tenho que agendar para pegar via I/O (vai rodar numa thread indeterminada). Os 'gets' compartilham o objeto, pois lá será preenchido o valor que será usado.
> O put é sempre assíncrono, e não compartilha o objeto.
Com esta explicação me parece que seu polimorfismo é (ou deveria ser )
em torno da classe base "Tarefa".
Você tem uma lista de tarefas, algumas são get e outras put.
A tarefa é uma classe que guarda o recurso.
Então porque não ter uma TarefaGet com um shared_ptr dentro e ter uma
TarefaPut com unique_ptr dentro?
Outra coisa:
O fato de algo ser compartilhado não indica que se deva usar o shared_ptr.
O shared_ptr deve ser usado quando é indeterminado quem deve deletar o
objeto. (nao sei se eh seu caso ou nao)
Se o tempo de vida é determinado , exemplo o recurso R é usado por A e
B, e A sempre vive mais que B, entao nao eh preciso usar shared_ptr.
Então... isso é *outra* forma de fazer. Eu queria algo como o any_ptr que eu propus, pois assim, qualquer outro novo tipo de tarefa será aceito de imediato. A idéia é não ter que criar uma nova classe para cada caso. Tenho GETs e PUTs hoje. No futuro posso ter QUERY, PRE-FETCH/PREPARE, etc... Não quero ter que me preocupar com isso. Quero uma interface que aceite tudo sem impor muita coisa.
> Outra coisa:
> O fato de algo ser compartilhado não indica que se deva usar o shared_ptr.
> O shared_ptr deve ser usado quando é indeterminado quem deve deletar o
> objeto. (nao sei se eh seu caso ou nao)
Não é determinado. Não sei se ficou claro, mas eu sei sim os casos de uso do shared_ptr e unique_ptr. Essa não é a questão. Não é se devo, ou quando ou algo assim. O ponto é que eu criei algo que pode ter que interagir com os dois tipos; e eu queria saber se o std:: ou boost:: já previa isso.
Putz! Isso sim responde minha pergunta.
Mas não, o meu shared_ptr não tem isso ainda.
Tks!
[snip]
> As primeiras propostas do any_ptr já tinham o sentimento de que era
> uma gambiarra.
> É uma gambiarra!
> Ninguém mais acha isso?
Nem sempre é. Bom, acho o nome any_ptr ruim. Pois ali ele
implica union-like. E any_ptr me lembra boost::any, esperaria
então algo que aceitasse qualquer tipo pointer-like e fizesse
type-erasure dessa forma. Pra mim any_ptr se parece mais
com um boost::variant com um operator-> que faz visitação
de operator->.
Anyway, para a tarefa dada, eu acho uma gambiarra sim, e
acho o uso de shared_ptr unicamente sim bem melhor.
Porém, me parece que o Gianni ainda não se decidiu se
há passagem de ownership do recurso ou não pela função,
o que torna o uso de qualquer unique_ptr ou shared_ptr
estranhos. Já que os mesmos implicam exatamente isso.
Se não houver transferência de ownership, uma simples
referência e raw-pointer são bem mais indicados, pois
implicam apenas "conhecimento", e não embutem
no tipo o ownership do pointed object.
> --
[]'s
A função que recebe o objeto não se preocupa se o objeto é shared ou unique, por ela tanto faz. Por isso eu queria saber se existia um place-holder para qualquer tipo de smart_ptr, ou alguma maneira de passar qualquer smart_ptr de forma padrão. Pelo que o Lamarão falou, o shared_ptr que está no último draft tem um construtor que recebe unique_ptr. Isso resolve parte do que queria, eu ainda vou ter o maior custo do shared_ptr quando não precisar, mas pelo menos não é gambiarra.
Vou continuar pesquisando uma outra forma de fazer isso. Se eu achar uma solução como o any_ptr que fiz que não seja tão feia, eu uso. Se não, só continuo convertendo shared em unique.
O problema é que o any_ptr também vai ter overhead, e chutaria que
provavelmente maior que shared_ptr (apesar de que só medindo pra
saber).
[snip]
> Eu acho que a melhor solucao para seu problema é separar a tarefa em 2
> tipos , um com unique_ptr e outro com shared_ptr.
> Se precisa tratar ambos de forma polimorfica, basta ter uma base em
> comum. (talvez ja tenha ate no seu caso)
Polimorfismo vai gerar overhead. Ownership único é apenas uma forma
especial de ownership compartilhado onde se compartilha com apenas um.
Acho que está-se procurando solução pra algo que está solucionado. E
essa solução que se irá encontrar será igual ou pior do que já se tem.
> -x-
> http://www.twitter.com/thradams
Bom, é um pouco mais complicado que isso. 1o. eu teriar que terminar a implementação com todos os recursos necessários (move/copy-ctors, operator=, etc.)
Supondo uma implementação 'perfeita', eu acho que teria um custo maior que o unique_ptr mas menor que o shared_ptr quando o any_ptr tiver um unique, e um custo maior que os dois quando o any_ptr tiver um shared. O custo porém pode ser pequeno; e aí é que precisaria medir isso para saber o custo vs. benefício. *Acho* que ao final, pode valer a pena, no meu caso, que simplesmente converter tudo para shared_ptr, só pelo fato de em uns 10%-30% das vezes (cute de quanto vou ter unique) eu não precise do thread-safety que o shared tem.
Estamos apenas na terra da especulação aqui. Mas você tem que ver que
cada chamada de operator->, copy-constructor etc será do tipo:
if(is_unique)
return unique.operator->();
else
return shared.operator->();
Ou então através de polimorfismo que possivelmente geraria
cache-misses na vtable.
O que é significativamente maior em footprint e branches. A não ser
que você espere utilizar uma plataforma bizarra de SMP que exija
sincronização entre mais de um barramento, talvez overall você tenha
uma performance melhor com shared_ptr.
De qualquer forma, só faria sentido depois de benchmarked e provado
que esse é um gargalo na minha opinião. Adicionar um any_pointer
vai adicionar uma abstração nova desnecessária, que não lhe entrega
nada que um shared_ptr não entrega também e sem saber se isso fará
diferença nenhuma na performance.
Se você tivesse argumentos de segurança, como unique_ptrs sendo
compartilhados sem querer, eu estaria mais inclinado a concordar.
Mas na verdade é o contrário, já que você precisa tratar um
any_pointer como um unique_ptr por causa dele, mas também
pode usar um shared_ptr. Assim bugs podem ser encobertos ao se
testar com shared_ptr.
> --
[]'s