Pergunta

Eu passei os últimos 4 anos em C #, então eu estou interessado em melhores práticas e padrões atuais de design comum em C ++. Considere o seguinte exemplo parcial:

class World
{
public:
    void Add(Object *object);
    void Remove(Object *object);
    void Update();
}

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this;
        }
    }
}

Aqui temos um mundo responsável pela gestão de um conjunto de objetos e atualizá-los regularmente. O fogo é um objeto que pode ser adicionado ao mundo em muitas circunstâncias diferentes, mas normalmente por outro objeto já está no mundo. O fogo é o único objeto que sabe quando ele queimou por isso atualmente eu tenho que apagar-se. O objeto que criou o fogo é provavelmente já não na existência ou relevantes.

Isso é uma coisa sensata a fazer ou há um projeto melhor que ajudar a limpar esses objetos?

Foi útil?

Solução

O problema com isto é que você está realmente criando um acoplamento implícito entre o objeto ea classe mundial.

Se eu tentar chamar Update () fora da classe Mundial, o que acontece? Eu poderia acabar com o objeto que está sendo excluído, e eu não sei porquê. Parece que as responsabilidades são mal misturado. Isso vai causar problemas no momento em que usar a classe de incêndio em uma nova situação que você não tinha pensado de quando você escreveu este código. O que acontece se o objeto deve ser excluído de mais de um lugar? Talvez ele deve ser removido tanto do mapa atual mundo, e inventário do jogador? Sua função Update irá removê-lo do mundo, e, em seguida, excluir o objeto, e da próxima vez que o mapa ou as tentativas de inventário para acessar o objeto, coisas ruins acontecem.

Em geral, eu diria que é muito intuitivo para uma função Update () para excluir o objeto que está sendo atualizado. Eu também diria que é intuitiva para um objeto para apagar-se. O objeto deve mais provável ter algum tipo de forma de acionar um evento dizendo que ele tem queima acabado, e qualquer pessoa interessada pode agora agir sobre isso. Por exemplo, removendo-o do mundo. Para excluí-lo, pense em termos de propriedade.

Quem possui o objeto? O mundo? Isso significa que o mundo sozinho começa a decidir quando as matrizes de objetos. Isso é bom, desde que a referência do mundo para o objeto vai durar mais que um outras referências a ele. Você acha que o objeto possui em si? Afinal, o que isso quer dizer? O objeto deve ser excluído quando o objeto não existe mais? não faz sentido.

Mas se não houver claramente definidos único proprietário, implementar propriedade compartilhada, por exemplo, usando um ponteiro inteligente implementação de contagem de referência, tais como boost::shared_ptr

Mas ter uma função membro no objeto em si, que é codificado para remover o objeto do um lista específica, se é ou não existe lá, e se é ou não também existe em qualquer outra lista e também excluir o objeto em si, independentemente de qual referências a ele existir, é uma má idéia.

Outras dicas

Você fez Update uma função virtual, o que sugere que as classes derivadas podem substituir a implementação de Update. Isto introduz duas grandes riscos.

1.) Uma implementação substituído podem lembrar de fazer um World.Remove, mas pode esquecer o delete this. A memória é perdida.

2.) A implementação substituído chama a Update da classe base, que faz um delete this, mas, em seguida, prossegue com mais trabalho, mas com um inválido esse ponteiro.

Veja este exemplo:

class WizardsFire: public Fire
{
public:
    virtual void Update()
    {
        Fire::Update();  // May delete the this-object!
        RegenerateMana(this.ManaCost); // this is now invaild!  GPF!
    }
}

Não há nada realmente errado com objetos em si apagar em C ++, a maioria das implementações de contagem de referência vai usar algo similar. No entanto, ele é visto como uma técnica ligeiramente esotérico, e você terá que manter um olho muito atento sobre as questões de propriedade.

A eliminação vai falhar e pode falhar o programa se o objeto era não alocado e construído com o novo. Esta construção vai impedi-lo de instanciar a classe na pilha ou estaticamente. No seu caso, você parece estar usando algum tipo de esquema de alocação. Se você ficar com esse, isso pode ser seguro. Eu certamente não iria fazê-lo, no entanto.

Eu prefiro um modelo de propriedade mais rigorosa. O mundo deve possuir os objetos nele e ser responsável pela limpeza-los. Quer ter remove mundo fazer isso ou ter atualização (ou outra função) retornar um valor que indica que um objeto deve ser suprimida.

Eu também estou supondo que neste tipo de padrão, você vai querer referência contar seus objetos para evitar acabar com ponteiros pendurados.

Ele certamente funciona para objetos criados por new e quando o chamador de Update está devidamente informados sobre esse comportamento. Mas eu gostaria de evitar. No seu caso, a propriedade está claramente no mundo, então eu faria o mundo excluí-lo. O objeto não cria em si, eu acho que também não deve excluir-se. Pode ser muito surpreendente se você chamar uma função "Update" no seu objeto, mas, de repente, esse objeto não está existindo mais sem mundo fazendo alguma coisa fora de si mesmo (para além de Removê-lo fora de sua lista - mas em outro quadro O chamado código atualização sobre o objeto não vai notar isso).

Algumas idéias sobre este

  1. Adicione uma lista de referências de objeto para Mundial. Cada objeto nessa lista está pendente para remoção. Esta é uma técnica comum, e é usado em wxWidgets para janelas de topo que foram fechadas, mas ainda pode receber mensagens. Com o tempo ocioso, quando todas as mensagens são processadas, a lista pendente é processado, e os objetos são excluídos. Acredito Qt segue uma técnica similar.
  2. Diga ao mundo que o objeto quer ser excluído. O mundo será devidamente informado e vai cuidar de qualquer coisa que precisa ser feito. Algo como um deleteObject(Object&) talvez.
  3. Adicione uma função shouldBeDeleted a cada objeto que retorna true se o objeto deseja que sejam apagadas por seu proprietário.

Eu preferiria a opção 3. O mundo chamaria Update. E depois disso, parece se o objeto deve ser excluído, e pode fazê-lo -. Ou se desejar, ele se lembra desse fato, adicionando esse objeto a uma lista de remoção pendente manualmente

É uma dor na bunda quando você não pode ter certeza de quando e quando não seja capaz de funções de acesso e dados do objeto. Por exemplo, em wxWidgets, há uma classe wxThread que pode funcionar em dois modos. Um desses modos (chamado "destacável") é que, se sua função principal retorna (e os recursos de rosca deve ser lançado), ele apaga-se (para liberar a memória ocupada pelo objeto wxThread) em vez de esperar para o proprietário do segmento objeto para chamar uma espera ou participar de função. No entanto, isto faz com que a dor de cabeça grave. Você nunca pode chamar quaisquer funções nele porque ele poderia ter sido terminado em quaisquer circunstâncias, e você não pode tê-lo não foi criado com nova. Muito algumas pessoas me disseram que não gostam muito de que o comportamento do mesmo.

O auto supressão de referência objecto é contado de cheiro, imho. Vamos comparar:

// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances. 
class Bitmap {
    ~Bitmap() {
        if(bmp->dec_refcount() == 0) {
            // count drops to zero => remove 
            // ref-counted object. 
            delete bmp;
        }
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount();
    int inc_refcount();

};

Compare isso com objetos refcounted auto-exclusão de:

class Bitmap {
    ~Bitmap() {
        bmp->dec_refcount();
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount() {
        int newCount = --count;
        if(newCount == 0) {
            delete this;
        }
        return newCount;
    }
    int inc_refcount();
};

Eu acho que o primeiro é muito mais agradável, e eu acredito referência bem concebido contados objetos fazer não que "excluir este", porque aumenta o acoplamento: A classe usando a referência contada dados tem de saber e lembre-se sobre que os dados em si exclui como um efeito colateral de decrementing a referência de contagem. Note como "bmp" torna-se, possivelmente, um apontador pendente no destruidor do ~ Bitmap. Indiscutivelmente, não fazendo isso "excluir este" é muito mais agradável aqui.

Resposta a uma pergunta semelhante "O que é o uso de eliminar este "

Isso é uma implementação bastante comum contagem de referência e eu usei isso com sucesso antes.

No entanto, eu também vi isso falhar. Eu gostaria de poder lembrar as circunstâncias do acidente. @abelenky é um lugar que eu já vi isso falhar.

Pode ter sido onde ainda Fire subclasse, mas não conseguem criar um destrutor virtual (veja abaixo). Quando você não tem um destrutor virtual, a função Update() chamará ~Fire() vez do ~Flame() destructor apropriada.

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this; //Make sure this is a virtual destructor.
        }
    }
};

class Flame: public Fire
{
private: 
    int somevariable;
};

Outros mencionaram problemas com "excluir este". Em suma, uma vez que "mundo" gere a criação de fogo, deve também gerir a sua supressão.

Um outro problema é se as extremidades do mundo com um fogo ainda está queimando. Se este é o único mecanismo através do qual um incêndio pode ser destruído, então você pode acabar com um órfão ou incêndio lixo.

Para a semântica que você quer, eu teria um ou bandeira "ativa" "vivo" na sua classe "Fire" (ou objeto se aplicável). Então o mundo seria de vez em quando verificar o seu inventário de objetos, e se livrar de aqueles que não está mais ativo são.

-

Uma nota mais é que seu código como escrito tem fogo herdar privada do objeto, uma vez que é o padrão, embora seja muito menos comum do que inheiritance público. Você provavelmente deve fazer o tipo de herança explícito, e eu suspeito que você realmente quer inheritiance público.

Não é geralmente uma boa idéia para fazer um "deletar essa" a menos que sejam necessários ou utilizados de uma forma muito simples. No seu caso parece que o mundo pode simplesmente excluir o objeto quando ele é removido, a menos que existam outras dependências não estamos vendo (caso em que a sua chamada de exclusão causará erros, uma vez que não são notificados). Mantendo propriedade fora de seu objeto permite que o objeto a ser melhor encapsulado e mais estável:. O objeto não se tornará inválido em algum momento simplesmente porque ele escolheu para se excluir

Não

Eu faço isso há nada de intrinsecamente errado com um objeto apagar em si, mas um outro método possível seria a de ter o mundo ser responsável por exclusão de objetos como parte da :: Remove (assumindo que todos os objetos removidas também foram excluídos.)

excluir este é muito arriscado. Não há nada "errado" com ele. É legal. Mas há tantas coisas que podem dar errado quando você usá-lo de que ele deve ser usado com moderação.

Considere o caso em que alguém tem ponteiros abertos ou referências para o objeto, e então ele vai e apaga-se.

Na maioria dos casos, eu acho que o objeto vida deve ser gerido pelo mesmo objeto que o cria. Ele apenas torna mais fácil saber onde ocorre a destruição.

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