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;
}
Foi útil?

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, deleteindicaçõ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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top