Você nao faz. Se este é invalido, qualquer operacao com ele (exceto
copy-construction, destrucao e assignment)
sao invalidas.
Você deve tomar cuidado quais operacoes voce executa sobre o container
e quais iteradores vc tem com ele.
> Obrigado a todos.
--
Felipe Magno de Almeida
Voce pode me apontar isto no padrao? Nao vejo como isto pode ser.
Voce nao esta confundindo o iterador retornado por end() com um
iterador invalido?
> --
>
>
> P.
Falou,
Bom, dei uma olhada. E quase nao aparece nada (C++03) para
invalid/invalidated/etcs.
Porem:
Em 24.1.3.1 temos:
— If a and b are equal, then either a and b are both dereferenceable
or else neither is dereferenceable.
Um iterador invalido me parece nao cumprir o requerimento de um
iterador nao-dereferenceable.
Por exemplo:
std::vector<char> v(10);
std::vector<char>::iterator end = v.end();
v.erase(boost::prior(v.end());
assert(v.end() == end); // Este assert dispara.
Acredito que um iterador invalido nao precisa cumprir nenhum
requerimento, exceto os de um objeto comum: Assignment e destruction.
Ateh copy-construction estou em duvida agora hehehe. Mas provavelmente eh ok.
> Porém, estou com muito sono pra ler o padrão agora e descobrir se essa
> analogia é furada neste caso.
Lol.
> Ele está aqui na minha mesa, amanhã logo que eu acordar eu olho.
Melhor uma versao eletronica pra buscas ;).
> Provavelmente alguém vai ser mais rápido que eu, de qualquer modo. :-)
>
> --
> P.
--
Felipe Magno de Almeida
C++0x diz em 24.1.10:
10 An invalid iterator is an iterator that may be singular.1)
E para singular em C++03 24.1.5:
5 Just as a regular pointer to an array guarantees that there is a
pointer value pointing past the last element of
the array, so for any iterator type there is an iterator value that
points past the last element of a corresponding
container. These values are called past-the-end values. Values of an
iterator i for which the expression *i
is defined are called dereferenceable. The library never assumes that
past-the-end values are dereferenceable.
Iterators can also have singular values that are not associated with
any container. [Example:
After the declaration of an uninitialized pointer x (as with int* x;),
x must always be assumed to have a
singular value of a pointer. ] Results of most expressions are
undefined for singular values; the only exception
is an assignment of a non-singular value to an iterator that holds a
singular value. In this case the singular
value is overwritten the same way as any other value. Dereferenceable
values are always nonsingular.
Em 24.1.1.2 na tabela:
X u(a); X post: u is a copy of a A destructor is assumed to be present
and accessible
Logo, ele pode ser destruido *e* assigned de um iterador valido.
O resto parece ser undefined behavior.
Você deveria fortemente repensar seu design.
Primeiro que utilizar um iterador invalido nao gera uma exceção necessariamente.
Faze-lo gera undefined behavior, o que significa que *qualquer coisa*
pode acontecer.
> Acho que esta solução não é a mais adequada, pois exceções causam/
> elevam o custo operacional e devem ser evitadas.
Você deve estar usando o Visual C++ junto com SEH. Por isto as excessões.
Você precisa antes de mais nada sincronizar os acessos ao container
(se já nao está fazendo isso), com
a ajuda de um mutex, e deve considerar que *todos* os iteradores
criados fora de uma critical section
como invalidos. Desta forma você só pode utilizar iteradores que foram
criados em uma parte
de código protegida pelo mutex, onde nenhum acesso de outra thread
ocorreu sobre o container.
> Mais uma vez obrigado pela ajuda.
Existem 2 defects reports sobre o assunto:
407 e 408.
Pelo 407, acho que nem copy-construction é permitido já que um
iterador singular (equivalente
a um iterador invalido) pode contar dados nao inicializados, e
copy-construction de
variaveis nao inicializadas possui undefined behavior.
[snip]
> Já descobri uma forma de tornar um iterator válido ao realizar uma
> remoção de um elemento : map_test.erase(iter_map_test++);
> Assim o iterator já retorna apontando para o próximo elemento ou para
> end()
A lista ja explicou que nao eh possivel fazer o que voce quer, mas voce nao quer
ouvir a lista. Se voce decide ignorar os conselhos da lista, fica
dificil te ajudar.
[snip]
Você poderia por favor, detalhar o cenário do seu problema.
Fique à vontade para responder em PVT se preferir.
2008/4/5 Felipe Magno de Almeida <felipe.m...@gmail.com>:
>
2008/4/6 Thiago Adams <thiago...@gmail.com>:
Em todas as implementacoes da STL que eu conheça nao é seguro.
E é exatamente por este motivo que a STL nao oferece thread-safety
maior que a de um inteiro.
[snip]
Você está com um problema sim de race condition. E o chato é que isso,
dependendo das características da implementação, pode não aparecer
frequentemente (aquele segfault que ocorre de tempos em tempos...).
Procura por race condition que certamente você vai encontrar o exemplo
clássico de manipulação de contas bancárias. Acho que com ele você vai
entender o problema.
Com relação ao seu projeto, como já citaram, existem algumas
abordagens. Se possível divulgue mais informações sobre o seu projeto
como quantidade de dados simultâneos, se tem um tempo de resposta
máximo, se o hardware onde vai rodar esse projeto tem "recursos
fartos" (se for rodar em hardware embarcado a conversa é outra...), e
qualquer outra informação que você achar pertinente.
Como o Thiago comentou, uma possibilidade é você deixar uma thread
responsável por alterações no conteiner (inserção, alteração,
exclusão). As outras threads realizam suas buscas/tarefas e, quando
necessário, solicitam alterações para a thread responsável por
alterações.
Outra abordagem seria colocar um mutex em cada registro, ou um mutex
associado a cada X registros. Com isso só a thread que tiver o mutex
pode manipular o dado. Mas isso depende muito da quantidade de
"registros" que você precisa manipular na memória em um tempo X.
Vou colocar 2 abordagens, mas elas dependem muito da quantidade de
dados simultâneos que você pode ter em um determinado momento dentro
do conteiner.
Trabalhei há um tempo atrás em um projeto similar ao seu, onde várias
threads podiam inserir alterar e excluir dados de um conteiner. O
número de threads era configurável, mas variava geralmente entre 8 e
16 (conforme a capacidade da máquina). Além disso, a quantidade de
informações que eu poderia ter simultaneamente era de aproximadamente
500.000 registros.
O tempo de resposta era fator crítico e o processamento realizado por
cada thread era praticamente o mesmo. Eu não tinha como deixar uma
thread apenas para manipular dados do conteiner porque senão ela
virava o gargalo do sistema. Além do que o hardware era um dual core
com 4 processadores.
Investimos um bom tempo tentando projetar uma arquitetura para
maximizar a concorrência entre as threads. Na época utilizamos um
pouco dos conceitos de buckets existentes no ORACLE. Para simplificar,
um bucket é um nome bonito para um conjunto de registros.
Definimos duas possíveis abordagens:
(1) Um hash (calculado sobre a chave de busca) que apontava para uma
lista de buckets. Esse hash não era extensível. O tamanho da lista de
buckets variava conforme a necessidade e a busca por um registro era
sequencial dentro dessa lista. Para cada lista existia um mutex
associado, ou seja, apenas uma thread podia manipular/percorrer os
dados dessa lista, mas outra thread poderia estar manipulando os dados
de outra lista de buckets.
(2) Um hash primário (calculado sobre a chave de busca) que apontava
para um hash secundário, e esse hash secundário apontava para o bucket
correto. Esse segundo hash era extensível. Neste caso existia um mutex
associado a cada bucket, e um no hash secundário para que uma thread
conseguisse acrescentar/remover buckets.
Como tinhamos uma interface bem definida para a forma de armazenamento
dos dados, era indiferente para o sistema a "abordagem de
armazenamento". Implementamos as duas abordagens e analizamos pontos
positivos e negativos. Pela análise que fizemos sobre as
características dos nossos dados, a gente optou pela primeira
abordagem.
Com mais informações sobre o seu projeto podemos ter melhores idéias :-)
Abraços !!!
[]'s
Alex Sandro Garzão
A thread responsável pelas modificações recebe as "tarefas" através de
uma lista, lista essa preenchida pelas outras threads. Essa lista sim
teria um mutex para inserir/remover tarefas. O tempo de cada lock deve
ser baixo para que essa arquitetura funcione com performance.
Seria, no final das contas, uma arquitetura produtor/consumidor, com a
ressalva que temos vários produtores para um consumidor, e o
consumidor é rápido o suficiente para consumir as N tarefas geradas
pelos produtores sem que o consumidor se torne o gargalo no sistema.
Bom, nada impede também que exista N consumidores :-)
Bom, propor arquiteturas é fácil, mas sem você ter uma idéia de como
os dados se comportam fica complicado. Nem que seja para você chegar a
conclusão que "não existe padrão" nos dados hehehe
> Esta técnica das listas hash é interessante.
> Parece haver uma troca entre o custo da sincronização e consequente
> perda de concorrência e o acréscimo de mais uma busca no algoritmo.
A gente aumentou o custo de processamento para uma busca, mas aumentou
o paralelismo real em máquinas SMP.
> Você tem medições de performance comparando as duas abordagens?
Não tenho mais esses dados precisamente :-/
Lembro que, com o nosso conjunto de dados, o desempenho era superior
nas buscas com hash duplo (meio óbvio) mas em toda uma bateria de
testes, o desempenho no final era inferior. Isso porque o segundo hash
é extensível e em determinado momento ele aumenta e/ou diminui o
número de entradas. Esse processo é muito caro computacionalmente
porque além do lock em todos esses buckets, se você altera o número de
buckets o hash deve ser recalculado para todas as chaves de todas os
buckets daquela hash secundário.
Para nós a versão com lista encadeada de buckets era, para uma bateria
de dados (aproximadamente 15 milhões de registros), aproximadamente
18% mais rápida. Se o número de registros simultâneos não variasse
muito pela linha do tempo, certamente o hash duplo seria bem mais
rápido.
[]'s
Alex Sandro Garzão
Mil desculpas... Não foi intencional :-)
[]'s
Alex Sandro Garzão
Se esse comentário fosse lá na empresa você teria acabado de receber
uma tarefa nova. :)
O nosso wiki é livre, logo...
Bom trabalho! :)
André
2008/4/10 Amanda Cristina <amaz...@gmail.com>:
> Pessoal,
>
> Sugestão, acho que é possível organizar este tópico e transformá-lo num
> artigo do wiki, não?
Se esse comentário fosse lá na empresa você teria acabado de receber
uma tarefa nova. :)
Parece que você foi voluntariada para essa tarefa. Que legal.
Foi o que a lista afirmou desde o inicio.
> Na verdade é muito simples, a partir do momento que um determinado
> ítem, que está sendo referenciado por um iterator qualquer, for
> deletado o iterator passa a apontar para uma área inválida.
Este é o comportamento de uma implementação comum, e normalmente
a mais rápida.
[snip]
> Por isto deve-se tomar muito cuidado com o uso de Iterator's,
> inclusive encontrei muitas recomendações de que seu uso deve ser o
> mais restrito possível.
Discordo veementemente. Como você pretende iterar sobre uma lista
sem usar iteradores?
Acredito que você está encarando o problema de forma errada, e por
isto está tendo problemas com iteradores.
[snip]
--
Felipe Magno de Almeida
Nao entendi o quote "qual será sua cara?".
> que eu já havia dito que
> minha implementação já está funcionando e muito bem,
Posso ter me enganado, mas pela thread entendi que você estava
recebendo exceções ao usar um interador inválido.
> que tal 1.000.000
> milhão de execuções dia, em 5 abr, 14:56,
Um milhão de milhões?
> e meu interesse era o de
> questionar sobre se existiria uma forma de testar se um Iterator é
> válido ou não.
>
> Apenas queria entender se isto poderia ser feito e claro, entender o
> motivo de tal comportamento, e não apenas : Não é possível.
Quando se fala em C++, presume-se o C++ padrão. E em C++ padrão
é dificil discutir implementação já que o padrão não obriga uma
implementação específica.
Também não percebi nenhuma pergunta sobre as razões do mesmo
nao ser garantido pelo padrão, apenas constatações de como um código
que invocava comportamento indefinido estava se comportando e a partir
deste comportamento circundar a inabilidade deste teste.
> Não sou um acadêmico nem muito menos um doutor em todas as áreas do
> assunto, mas para um bom programador em C, entender de ponteiros é o
> básico, por isto minha analogia e obviamente o correspondente efeito.
Sim. Mas não acredito que contestei nada disto. Só afirmei que discordo
das recomendações que recebeste sobre restringir o uso de iteradores.
Esta é, IMHO, uma recomendação muito ruim.
> Assunto esgotado.
Ok.
> Obrigado.
Rodrigo Strauss
2008/5/2 lsalamon <lsal...@gmail.com>:
Novamente, as diferenças entre cientistas e engenheiros...
Boa pergunta!!!
2008/5/3 Rodrigo Strauss <rod...@1bit.com.br>: