Excluindo um elemento de um vetor de ponteiros em C ++
-
27-09-2019 - |
Pergunta
Lembro -me de ouvir que o código a seguir não é compatível com C ++ e esperava que alguém com muito mais C ++ legalese do que eu pudesse confirmar ou negar.
std::vector<int*> intList;
intList.push_back(new int(2));
intList.push_back(new int(10));
intList.push_back(new int(17));
for(std::vector<int*>::iterator i = intList.begin(); i != intList.end(); ++i) {
delete *i;
}
intList.clear()
A lógica era que é ilegal para um vetor conter ponteiros para a memória inválida. Agora, obviamente, meu exemplo vai compilar e até funcionará em todos os compiladores que eu conheço, mas é o C ++ compatível com padrão ou devo fazer o seguinte, o que me disseram é de fato a abordagem padrão compatível:
while(!intList.empty()) {
int* element = intList.back();
intList.pop_back();
delete element;
}
Solução
Seu código está bem. Se você está preocupado com algum motivo sobre os elementos serem inválidos momentaneamente, mude o corpo do loop para
int* tmp = 0;
swap (tmp, *i);
delete tmp;
Outras dicas
Seu código é válido, mas a melhor solução será usar ponteiros inteligentes.
O problema é que todos os requisitos para std::vector
estão localizados na seção 23.2.4 do padrão C ++. Não há limitações sobre ponteiros inválidos. std::vector
funciona com int*
Como em qualquer outro tipo (não consideramos o caso de vector<bool>
), isso não se importa onde eles estão apontando.
A filosofia C ++ é permitir o programador o máximo possível de latitude e proibir apenas coisas que realmente causarão danos. Ponteiros inválidos não causam mal em si mesmos e, portanto, você pode tê -los livremente. O que causará danos é usar o ponteiro de qualquer forma e, portanto, invoca comportamentos indefinidos.
Por fim, isso é uma questão de gosto pessoal mais do que qualquer coisa. Não é "padrões não compatíveis" ter um vetor que contém indicações inválidas, mas é perigoso, assim como é perigoso ter qualquer ponteiro que aponte para a memória inválida. Seu último exemplo garantirá que seu vetor nunca contenha um ponteiro ruim, sim, por isso é a escolha mais segura.
Mas se você sabia Que o vetor nunca seria usado durante o loop do seu ex -exemplo (se o vetor for escopo localmente, por exemplo), é perfeitamente bom.
onde você ouviu isso? Considere isto:
std::vector<int *> intList(5);
Acabei de criar um vetor cheio de 5 ponteiros inválidos.
Ao armazenar ponteiros cruas em um contêiner (eu não recomendaria isso) e depois ter que fazer uma exclusão de 2 fases, eu escolheria sua primeira opção no segundo.
Acredito que o contêiner :: clear () excluirá o conteúdo do mapa com mais eficiência do que exibir um único item por vez.
Você provavelmente poderia transformar o loop para um bom (psuedo) forall(begin(),end(),delete)
E torná -lo mais genérico, por isso nem importava se você mudou de vetor para outro contêiner.
Não acredito que isso seja uma questão de conformidade com os padrões. Os padrões C ++ definem a sintaxe dos requisitos de idioma e implementação. Você está usando o STL, que é uma biblioteca poderosa, mas, como todas as bibliotecas, não faz parte do próprio C ++ ... embora eu ache que se possa argumentar que, quando usado de forma agressiva, bibliotecas como STL e QT estendem o idioma a uma linguagem super -super -super -superficial .
Ponteiros inválidos são perfeitamente compatíveis com os padrões C ++, o computador simplesmente não vai gostar quando você os desreferencia.
O que você está perguntando é mais uma pergunta de práticas recomendadas. Se o seu código for multithread e intList
é potencialmente compartilhado, então sua primeira abordagem pode ser mais perigosa, mas como Greg sugeriu se você souber disso intList
Não pode ser acessado, então a primeira abordagem pode ser mais eficiente. Dito isto, acredito que a segurança geralmente deve vencer em uma troca até que você saiba que há um problema de desempenho.
Conforme sugerido pelo projeto pelo conceito de contrato, todo o código define um contrato, implícito ou explícito. O problema real com código como esse é o que você está prometendo ao usuário: pré -condições, postagens, invariantes etc. As bibliotecas fazem um determinado contrato e cada função que você escreve define seu próprio contrato. Você só precisa escolher o equilíbrio apropriado para o seu código e, desde que você deixe claro para o usuário (ou a si mesmo de seis meses) o que é seguro e o que não é, tudo bem.
Se houver práticas recomendadas com uma API, use -as sempre que possível. Eles provavelmente são práticas recomendadas por um motivo. Mas lembre -se, uma prática recomendada pode estar nos olhos de quem vê ... ou seja, eles podem não ser uma prática recomendada em todas as situações.
É ilegal para um vetor conter ponteiros para a memória inválida
É isso que o padrão tem a dizer sobre o conteúdo de um contêiner:
(23.3): O tipo de objetos armazenados nesses componentes deve atender aos requisitos de Copicável tipos (20.1.3) e os requisitos adicionais de Atribuível tipos.
(20.1.3.1, copiactonscrutível): Na Tabela 30 a seguir, T é um tipo a ser fornecido por um programa C + + instantando um modelo, T é um valor do tipo T e U é um valor do tipo const T.
expression return type requirement
xxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
T(t) t is equivelant to T(t)
T(u) u is equivelant to T(u)
t.~T()
&t T* denotes the address of t
&u const T* denotes the address of u
(23.1.4, atribuível): 64, t é o tipo usado para instanciar o contêiner, t é um valor de t e u é um valor de (possivelmente const) t.
expression return type requirement
xxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
t = u T& t is equivilant to u
Isso é tudo o que diz sobre o conteúdo de uma coleção STL. Não diz nada sobre ponteiros e é particularmente silencioso sobre os ponteiros que apontam para a memória válida.
Portanto, delete
indicações em um vector
, embora provavelmente uma decisão arquitetônica muito ruim e um convite para a dor e o sofrimento do depurador às 3:00 da manhã de sábado à noite, é perfeitamente legal.
EDITAR:
Em relação ao comentário de Kranar que "atribuir um ponteiro a um valor de ponteiro inválido resulta em comportamento indefinido". Não, isso está incorreto. Este código é perfeitamente válido:
Foo* foo = new Foo();
delete foo;
Foo* foo_2 = foo; // This is legal
O que é ilegal é tentar fazer algo com esse ponteiro (ou foo
, para esse assunto):
delete foo_2; // UB
foo_2->do_something(); // UB
Foo& foo_ref = *foo_2; // UB
Simplesmente criar um ponteiro selvagem é legal de acordo com o padrão. Provavelmente não é uma boa ideia, mas legal, no entanto.
Edit2:
Mais do padrão em relação aos tipos de ponteiro.
Então diga o padrão (3.9.2.3):
... Um valor válido de um tipo de ponteiro de objeto representa o endereço de um byte na memória (1.7) ou um ponteiro nulo (4.10) ...
... e sobre "um byte na memória" (1.7.1):
A unidade de armazenamento fundamental no modelo de memória C + + é o byte. Um byte é pelo menos grande o suficiente para conter qualquer membro do conjunto de caracteres básicos de execução e é composto por uma sequência contígua de bits, cujo número é definido por implementação. O bit menos significativo é chamado de bit de ordem baixa; O bit mais significativo é chamado de bit de alta ordem. A memória disponível para um programa C + + consiste em uma ou mais sequências de bytes contíguos. Todo byte tem um endereço único.
Não há nada aqui nesse byte fazendo parte de uma vida Foo
, sobre você ter acesso a ele ou qualquer coisa do tipo. É apenas um byte na memória.