避免dynamic_cast的替代设计

Alternative design to avoid dynamic_cast?

本文关键字:dynamic cast 避免      更新时间:2023-10-16

假设我有Archive接口和File接口。

  • 每个File保证至少有 std::string name .
  • 每个Archive都可以std::vector<File*> Archive::list() const其文件。
  • 每个Archive都可以Archive::extract(std::vector<File*> files)

然后我有ZipArchiveZipFileZipFile包含存档文件中的偏移量和其他实现细节。然后是TarArchive/TarFile等等。这些中的每一个都用ZipFileTarFile等实例填充std::vector<File*> list() const

list()旨在让用户有机会选择要解压缩的文件。他们从该向量中选择元素,然后将该向量传递给extract()

此时,ZipArchive需要假定它传递了正确的类型,并dynamic_cast<ZipFile*>(file)访问实现详细信息。

这感觉很糟糕。这是可以接受的吗?有其他选择吗?

如注释中所建议的,您可以将提取界面从Archive移动到File。归档将返回std::vector<File*>,但实际上每个对象都将是,例如,ZipFile,并且将知道它属于哪个归档以及它的类型是什么,并且能够调用正确的提取方法。

因此,您可以拥有代码而无需检查存档类型:

struct File;
struct Archive {
    virtual std::vector<File*> fileList() = 0;
};
struct File {
    File(std::string name_) : name(name_) {}
    virtual void extract() = 0;
    std::string name;
};
struct ZipFile;
struct ZipArchive: public Archive {
    void extractFile(ZipFile& file);
    virtual std::vector<File*> fileList();
};
struct ZipFile: public File {
    ZipArchive* archive;
    virtual void extract() { archive->extractFile(*this); }
    ZipFile(std::string name_, ZipArchive* archive_) : File(name_), archive(archive_) {}
};

完整示例:http://ideone.com/kAs5Jc

如果你想通过一次调用提取许多文件,它可能更困难,但你可以让存档的extractFile只记住该文件,然后在Archive类中使用特殊方法来一次提取所有记住的文件。我认为这甚至可以隐藏在一个相当简单的界面下。

您的ZipArchive可以在其文件列表中搜索传递的指针。如果它在那里,它可以使用存储的指针(已经是 ZipFile 类型),也可以static_cast传递给ZipFile的指针(因为你已经证明了它的类型)。如果传递的指针不在列表中,则它显然不是此存档拥有的文件,因此您可以继续进行错误处理。

您还可以向每个File添加类型 Archive* 的反向指针。具体的ZipArchive实现可以通过简单的指针比较来检查其文件之一。

void ZipArchive::extract(std::vector<File*> files) 
{
    for (auto file : files)
    {
        if (file->archive() == this) 
        {
            // one of my files
            auto zipFile = static_cast<ZipFile*>(file);
            // do something with zipFile
        }
        else
        {
            // file is owned by some other archive
        }
    }
}
class Archive { 
public:
    static int registerArchiveType(const std::string &name) {
        // generate a unique int for the requested name archive type
        // and store it in a map or whatever
        return uniqueInt;
    }
    int archiveType() const;
protected: 
    Archive(int type) : _type(type) {}
private: 
    int _type;
public:
     virtual extract(std::vector<File*> files);
    // your implementation details
};
class File {
public:
    int archiveType() { return _archType; }
protected:
    // force implementations to pass the same type
    // they received from the call to Archive::registerArchiveType
    File() {}
    void setArchiveType(const std::string &archiveType) {
        // multiple calls to registerArchiveType return the
        // same identifier if passed the same string
        _archiveType = Archive::registerArchiveType(archiveType);
    }
private:
    int _archiveType;
};

然后在ZipArchive实现中,在 extract 方法中,如果 archiveType 返回的 int 与为 Zip 存档类型注册的 int 相同,则可以执行static_cast而不是动态 int。

static const char* ZIP_TYPE = "zip";
// specialize your ZipFile making sure
// you pass the correct archive type identifier
// in the constructor
class ZipFile {
public:
    ZipFile() : File() {
        setArchiveType(ZIP_TYPE);
    }
    // bla bla
};
void ZipArchive::extract(std::vector<File*> files) {
    for (int i = 0; i < files.count(); i++) {
        if (files[i]->archiveType() == Archive::registerArchiveType(ZIP_TYPE)) {
            ZipFile *zipFile = static_cast<ZipFile*>(files[i]);
            // do something with zipFile
        }
    }
}

你需要分析你对待Archive的方式:你是否需要在某些地方有一些由于不确定的类型而出现的常见行为?这将带来两种不同的设计选择,因此请谨慎选择。


正如评论中所说,您似乎不需要前者。
File表示文件句柄和ZipFileTarFile其派生。然后,对于每种类型的文件,让Archive处理它。

struct ZipFile 
{        
     File handle; 
     // zip-specific implementation details 
};
struct TarFile
{ 
     File handle;
     // tar-specific implementation details
};
class ZipArchive
{
     public:
            std::vector<ZipFile> list() const; 
            void extract(std::vector<ZipFile>);         
     private:
             std::vector<ZipFile> archive;
};

TarArchive也是如此.不再需要处理所有权、指针等;您还可以获得强大的类型安全性。