需要对派生类进行动态转换:寻找替代方法

Posted

技术标签:

【中文标题】需要对派生类进行动态转换:寻找替代方法【英文标题】:A need for dynamic cast of a derived class: looking for an alternative approach 【发布时间】:2019-07-15 15:05:49 【问题描述】:

我以这种简单的形式提出我的问题:

class animal 
public:
    animal() 
        _name="animal";
    

    virtual void makenoise()
        cout<<_name<<endl;
    

    string get_name()
        return _name;
    

protected:
    string _name;
;

class cat : public animal 
public:
    cat() 
        this->_name="cat";
    
;

class dog : public animal 
public:
    dog() 
        this->_name = "dog";
    
;

我想将所有动物类型一起存储在一个容器中,例如:

vector<animal*> container;
barnyard.push_back(new animal());
barnyard.push_back(new dog());
barnyard.push_back(new cat());

在我的代码中,我需要将狗对象转换为猫对象。而我需要从这个转换中建立一个新的狗对象并将其替换为与猫对应物相同的索引号。据我了解,dynamic_cast 在这种情况下不起作用,并且基于C++ cast to derived class,有人提到这种转换不是一个好习惯。由于我的模型中的猫和狗具有不同的行为特性,我不想将它们的定义放入动物模型中。另一方面,将它们分别存储在不同的向量中将很难处理。有什么建议吗?

【问题讨论】:

把狗变成猫有什么意义?如果您在猫所在的​​地方需要一只狗,只需擦除猫并插入一只狗。 矢量 您能否提供一些背景资料,说明您为什么需要这样的替换?必须将所有 dogs 替换为 cats 让我认为将两者都存储为 animal 的公共集合不是正确的选择,因为它会丢失该信息(这会导致您稍后需要动态转换) dynamic_cast 不转换任何对象。给定一个基类指针,它会为您提供一个指向派生类的指针(如果基类不是派生类型,则为 null)。这与转换对象完全无关(在您的简单示例中也没有任何意义)。我想你可能对对象和指向对象的指针有些混淆。 @SergeyA 是的,试图擦掉猫身上的污垢会很痛苦。这就是我养狗的原因;) 【参考方案1】:

你说:

我需要将狗对象转换为猫对象。

然后:

我需要从这个转换中建立一个新的狗对象并将其替换为与猫对应物相同的索引号。

您需要转换或替换它吗?那是完全不同的操作。

要进行转换,您需要设置一个带狗并返回猫的函数:

auto convertDogToCat(Dog const& dog) -> Cat 
    auto cat = Cat;

    // fill cat's member using dog's values...

    return cat; 

但要替换只需重新分配一个新的:

//      v--- a cat is currently there
barnyard[ii] = new Dog;
//           ^--- we replace the old pointer
//                with one that points to a dog.

但这会造成内存泄漏,要消除泄漏,只需使用std::unique_ptr

#include <memory> // for std::unique_ptr

// The base class need a virtual destructor
class animal 
public:
    virtual ~animal() = default;

    // other members...
;

std::vector<std::unique_ptr<animal>> barnyard;
barnyard.emplace_back(std::make_unique<animal>());
barnyard.emplace_back(std::make_unique<dog>());
barnyard.emplace_back(std::make_unique<cat>());

barnyard[ii] = std::make_unique<Dog>();

【讨论】:

【参考方案2】:

这是另一种方法。不使用 OOP 或动态调度,但为您的示例提供相同的功能。也快得多,因为分配/释放不需要动态内存,动物是单字节。

enum struct eAnimalKind : uint8_t

    Generic = 0,
    Cat = 1,
    Dog = 2,
;

string get_name( eAnimalKind k )

    static const std::array<string, 3> s_names =
    
        "animal"s, "cat"s, "dog"s
    ;
    return s_names[ (uint8_t)k ];


void makenoise( eAnimalKind k )

    cout << get_name( k ) << endl;

如果你的类比一个类型保持更多的状态,使用一个以该枚举作为成员的类。

如果某些动物使用自定义的字段/属性集,它会变得棘手但仍然可能,嵌套结构用于特定于物种的状态,以及这些结构在 class animal 中的 std::variant 以跟踪物种并保留数据.在这种情况下,您不再需要 enum eAnimalKind,std::variant 已经跟踪它包含的类型。

经典 C++ OOP 需要动态内存。派生类通常具有不同的 sizeof,不能将它们保存在单个向量中,只能保存指针,并且在运行时访问每个元素时会遇到 RAM 延迟。

如果您的动物又大又复杂,即兆字节的 RAM 和昂贵的方法,那很好。但是如果你的动物很小,包含几个字符串/数字,而且你有很多,RAM 延迟会破坏 OOP 方法的性能。

【讨论】:

这是一个不错的方法,但由于复杂性原因不适合我的问题 @JalilNourisa std::variant 在这方面非常好:让您拥有多个类型的对象而无需动态分配。我发现它在适用时比继承 + 虚拟调度简单得多。它大大降低了复杂性。

以上是关于需要对派生类进行动态转换:寻找替代方法的主要内容,如果未能解决你的问题,请参考以下文章

对象数组(基本情况)和派生类之间的动态转换

C++ 类和继承错误:未定义对派生类的引用

使用纯多态性和继承从基类调用派生类上的函数而不进行强制转换?

5继承与派生3-类型兼容规则

类型兼容规则

C++ 类型将基础对象转换为派生对象