Classes base abstratas e construção de cópias, regras práticas

9

Muitas vezes, é uma boa ideia ter uma classe base abstrata para isolar a interface do objeto.

O problema é que a construção de cópias, IMHO, é praticamente quebrada por padrão em C ++, com os construtores de cópias sendo gerados por padrão.

Então, quais são as dicas quando você tem uma classe base abstrata e ponteiros brutos nas classes derivadas?

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

E agora você desativa de maneira limpa a construção de cópias para toda a hierarquia? Declarar construção de cópia como particular em IAbstract?

Existem regras de três com classes base abstratas?

Codificador
fonte
11
referências usar em vez de ponteiros :)
TP1
@ t1: ou pelo menos algum ponteiro inteligente.
Benjamin Bannier
11
Às vezes, você só precisa trabalhar com o código existente ... Você não pode mudar tudo em um instante.
Coder
Por que você acha que o construtor de cópias padrão está quebrado?
12123
2
@ Codificador: O guia de estilo do Google é uma pilha de lixo e absolutamente péssimo para qualquer desenvolvimento de C ++.
DeadMG

Respostas:

6

A construção de cópias em uma classe abstrata deve ser tornada privada na maioria dos casos, assim como o operador de atribuição.

Classes abstratas são, por definição, feitas para serem do tipo polimórfico. Portanto, você não sabe quanta memória sua instância está usando e, portanto, não pode copiá-la ou atribuí-la com segurança. Na prática, você corre o risco de cortar: /programming/274626/what-is-the-slicing-problem-in-c

O tipo polimórfico, em C ++, não deve ser manipulado por valor. Você os manipula por referência ou por ponteiro (ou qualquer ponteiro inteligente).

Essa é a razão pela qual o Java tornou o objeto manipulável apenas por referência, e por que o C # e o D têm a separação entre classes e estruturas (a primeira sendo polimórfica e o tipo de referência, a segunda sendo não polimórfica e o tipo de valor).

deadalnix
fonte
2
As coisas em Java não são melhores. Em Java, é doloroso copiar qualquer coisa, mesmo quando você realmente precisa e é muito fácil esquecer de fazê-lo. Então você acaba com bugs desagradáveis, onde duas estruturas de dados têm uma referência à mesma classe de valor (por exemplo, Data) e, em seguida, uma delas altera a Data e a outra estrutura de dados agora está quebrada.
Kevin cline
3
Meh. Não é um problema - qualquer pessoa que conheça C ++ não cometerá esse erro. Mais importante, não há nenhuma razão para manipular tipos polimórficos por valor, se você souber o que está fazendo. Você está iniciando uma reação total contra uma possibilidade tecnicamente pequena. Ponteiros nulos são um problema maior.
DeadMG
8

Você poderia, é claro, apenas torná-lo protegido e vazio, para que classes derivadas possam escolher. No entanto, de maneira geral, seu código é proibido de qualquer maneira, porque é impossível instanciar IAbstract- porque ele possui uma função virtual pura. Como tal, isso geralmente não é um problema - suas classes de interface não podem ser instanciadas e, portanto, nunca podem ser copiadas, e suas classes mais derivadas podem banir ou continuar copiando conforme desejarem.

DeadMG
fonte
E se houver uma hierarquia Resumo -> Derivado1 -> Derivado2 e Derivado2 for atribuído a Derivado1? Esses cantos escuros da língua ainda me surpreendem.
12/12
@ Codificador: Isso geralmente é visto como PEBKAC. No entanto, mais diretamente, você sempre pode declarar um particular operator=(const Derived2&)em Derived1.
DeadMG
Ok, talvez isso realmente não seja um problema. Eu só quero ter certeza de que a classe está segura contra abusos.
12/12
2
E por isso você deve receber uma medalha.
World Engineer
2
@ Codificador: Abuso por quem? O melhor que você pode fazer é facilitar a gravação do código correto. Não faz sentido tentar se defender contra programadores que não conhecem C ++.
kevin Cline
2

Tornando privado o ctor e a atribuição (ou declarando-os como = delete no C ++ 11), você desativa a cópia.

O ponto aqui é onde você tem que fazer isso. Para manter seu código, o IAbstract não é um problema. (observe que, ao fazer o que você fez, você atribui o *a1 IAbstractsubobjeto a a2, perdendo qualquer referência a Derived. A atribuição de valor não é polimórfica)

A questão vem com Derived::theproblem. Copiar um Derivado para outro pode de fato compartilhar os *theproblemdados que não podem ser projetados para serem compartilhados (há duas instâncias que podem chamar delete theproblemseu destruidor).

Se for esse o caso, é Derivedque deve ser não copiável e não atribuível. Obviamente, se você tornar a cópia privada IAbstract, uma vez que a cópia padrão Derivedprecisa, Derivedela também não poderá ser copiada. Mas se você definir o seu próprio Derived::Derived(const Derived&)sem chamar IAbtractcopiar, ainda poderá copiá-los.

O problema está em Derivado e a solução deve permanecer em Derivado: se deve ser um objeto somente dinâmico acessado apenas por ponteiros ou referências, é o próprio Derivado que deve ter

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

Essencialmente, cabe ao designer da classe Derivado (que deve saber como o Derivado funciona e como theproblemé gerenciado) decidir o que fazer com a atribuição e a cópia.

Emilio Garavaglia
fonte