Como escrever um cctor e op= para uma classe de fábrica com ptr para abstrair o campo de membro?
-
22-09-2019 - |
Pergunta
Estou extraindo arquivos de arquivos zip e rar em buffers brutos.Eu criei o seguinte para agrupar minizip e unrarlib:
Arquivo.hpp - Usado para acessar tudo.Se eu pudesse tornar todas as funções das outras classes inacessíveis de fora, eu o faria.(Na verdade, suponho que eu poderia ser amigo de todas as outras classes no Archive e usar retornos de chamada de funções privadas..., mas isso é muito indireto.)
#include "ArchiveBase.hpp"
#include "ArchiveDerived.hpp"
class Archive {
public:
Archive(string path) {
/* logic here to determine type */
switch(type) {
case RAR:
archive_ = new ArchiveRar(path);
break;
case ZIP:
archive_ = new ArchiveZip(path);
break;
case UNKNOWN_ARCHIVE:
throw;
break;
}
}
Archive(Archive& other) {
archive_ = // how do I copy an abstract class?
}
~Archive() { delete archive_; }
void passThrough(ArchiveBase::Data& data) { archive_->passThrough(data); }
Archive& operator = (Archive& other) {
if (this == &other) return *this;
ArchiveBase* newArchive = // can't instantiate....
delete archive_;
archive_ = newArchive;
return *this;
}
private:
ArchiveBase* archive_;
}
ArquivoBase.hpp
class ArchiveBase {
public:
// Is there any way to put this struct in Archive instead,
// so that outside classes instantiating one could use
// Archive::Data instead of ArchiveBase::Data?
struct Data {
int field;
};
virtual void passThrough(Data& data) = 0;
/* more methods */
}
ArquivoDerivado.hpp "Derivado" sendo "Zip" ou "Rar"
#include "ArchiveBase.hpp"
class ArchiveDerived : public ArchiveBase {
public:
ArchiveDerived(string path);
void passThrough(ArchiveBase::Data& data);
private:
/* fields needed by minizip/unrarlib */
// example zip:
unzFile zipFile_;
// example rar:
RARHANDLE rarFile_;
}
ArquivoDerivado.cpp
#include "ArchiveDerived.hpp"
ArchiveDerived::ArchiveDerived(string path) { //implement }
ArchiveDerived::passThrough(ArchiveBase::Data& data) { //implement }
Alguém sugeriu que eu usasse esse design para poder fazer:
Archive archiveFile(pathToZipOrRar);
archiveFile.passThrough(extractParams); // yay polymorphism!
Como escrevo um cctor para Archive?
E quanto a op= para Arquivo?
O que posso fazer sobre "renomear"
ArchiveBase::Data
paraArchive::Data
?(Tanto o minizip quanto o unrarlib usam essas estruturas para entrada e saída.Os dados são genéricos para Zip & Rar e posteriormente são usados para criar a respectiva estrutura da biblioteca.) Todo o resto é acessado viaArchive
, e eu gostaria de declararData
em uma aula externa desta forma também.
Eu sei que poderia jogar fora minha corrente class Archive
, nome ArchiveBase
em Archive
, e use uma função de fábrica global.No entanto, eu queria evitar o uso da função global.
Solução
Primeiro de tudo, você não pode "copiar" uma classe abstrata porque não pode instanciar uma.Em vez disso, o que você deve fazer é configurar um std::tr1::shared_ptr dessa classe e passar um ponteiro.
Archive(ArchiveBase *_archiveBase)
Use uma função de fábrica fora da classe Archive para instanciação.
Archive createArchive(string _path, int _type){
switch(type) {
case RAR:
return Archive( new ArchiveRar(path) );
case ZIP:
return Archive( new ArchiveZip(path) );
case UNKNOWN_ARCHIVE:
throw exception("Unknown archive format");
break;
default:
throw exception("Improper archive type");
}
Para o operador =, simplesmente segurar um ponteiro inteligente como este e usar o "=" realizará a transferência segura de conhecimento entre as classes.Ele executa a contagem de referência e excluirá o ponteiro para que você não precise fazer isso e somente quando for seguro fazê-lo.
Archive& operator = (Archive& other) {
m_ArchiveBasePtr = other.m_ArchiveBasePtr;
return *this;
}
Deixe que os ponteiros inteligentes se preocupem em excluir, copiar e tudo mais para você.
Outras dicas
Trigos a sugestão funciona quando você pode pagar cópias superficiais e um relacionamento N-1.Ele falha quando as subclasses ArchiveBase contêm dados individuais específicos para cada instância do Archive e não são compartilhados entre vários objetos normalmente.
Uma abordagem alternativa para uma função createArchive() global é adicionar um método clone virtual abstrato() ao ArchiveBase e, em seguida, defini-lo em cada subclasse (ArchiveZip,ArchiveRar) para replicar adequadamente uma cópia superficial ou profunda conforme necessário.
Você pode então chamar archive_.clone() do construtor de cópia do Archive ou operator= se archive_ não for NULL.(Certifique-se de excluí-lo (gratuitamente) depois!)
O que posso fazer para "renomear" ArchiveBase::Data para Archive::Data?(Tanto o minizip quanto o unrarlib usam essas estruturas para entrada e saída.Os dados são genéricos para Zip e Rar e posteriormente são usados para criar a respectiva estrutura da biblioteca.)
Existem várias opções:
Archive::passThrough() { archive_ -> passThrough( this->getData() ) }
Archive::passThrough() { archive_ -> passThrough( this ) }
Ou você pode manter uma referência retroativa no ArchiveBase para o objeto Archive correspondente, que pode então ser consultado em busca de dados.
Embora tenha cuidado!É fácil dessincronizar essas informações duplicadas.(E você pode entrar em loops de arquivo de cabeçalho.) É por isso que sou a favor de passar o esse ponteiro ao redor!Pode sempre pré-declarar "class Archive;" e depois usar os ponteiros Archive* sem incluir Archive.hpp no ficheiro de cabeçalho.(Embora você ainda precise incluir Archive.hpp no arquivo .cpp.)