不带指针的c++多态性

C++ polymorphism without pointers

本文关键字:c++ 多态性 指针      更新时间:2023-10-16

编辑:一个更好的标题应该是:多态的大型对象集合没有单独的堆分配。

假设我有一个基类Animal,其中包含虚函数和一些派生类(Cat, Dog等)。真正的派生类包含4-8字节的数据。我想存储一个std::list<Animal>,它实际上包含了派生对象的项目。我想使用new.

来避免在堆上创建许多小对象。

是否有任何设计模式可以用来实现这一点?

编辑:我的想法来实现这个

    create std::deque<Cat>, std::deque<Dog>,…;存储std::list<Animal*>,包含来自deques的指针;我使用std::deque,因为我认为它有一个很好的内存管理与块的对象;

最终,没有。

多态性只适用于非值类型:引用和指针。由于引用只能绑定一次,因此不能在标准容器中真正使用它们。这就剩下指针了。

你找错了方向。如果您担心分配大量小对象的开销(我假设这是一个合理的担忧)。也就是说,您有实际的分析数据或足够的经验来了解(这是您的特定的应用程序的关注点),那么您应该修复。更改为这些对象分配内存的方式。创建一个小的分配堆或其他东西

诚然,c++ 0x之前的分配器在这方面有些欠缺,因为它们必须是无状态的。但就你的目的而言,你应该能够处理它。


从你的编辑:

这是一个糟糕的主意。从std::deque位置擦除,但开始或结束将使std::list中的每个指针无效。根据你的评论,这个想法是可行的。然而,为不同类型的对象拥有所有这些不同的内存块似乎违背了继承的全部要点。毕竟,你不能只是写一个新的Animal类型,然后把它塞进std::list;你必须为它提供内存管理。

您确定这里需要的是基于继承的多态性吗?您确定其他的方法不会同样有效吗?

我知道这个问题很老了,但是我找到了一个不错的解决办法。

假设:

您提前知道所有的派生类(给定您的编辑,这是真的)。

技巧:

使用boost::variant (http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html)

示例类:

class Animal {
public:
    virtual void eat() = 0;
};
class Cat : public Animal {
    virtual void eat() final override {
        std::cout << "Mmh, tasty fish!" << std::endl;
    }
};
class Dog: public Animal {
    virtual void eat() final override {
        std::cout << "Mmh, tasty bone!" << std::endl;
    }
};

/例子变体参观者:

typedef boost::variant<Cat, Dog> AnimalVariant;
class AnimalVisitor : public boost::static_visitor<Animal&> {
public:
    Animal& operator()(Cat& a) const {
        return a;
    }
    Animal& operator()(Dog& a) const {
        return a;
    }
};

示例用法:

std::vector<AnimalVariant> list;
list.push_back(Dog());
list.emplace_back(Cat());
for(int i = 0; i < 5; i++) {
    for(auto& v : list) {
        Animal& a = v.apply_visitor(AnimalVisitor());
        a.eat();
    }
}

示例输出

Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!

如果您担心分配许多小堆对象,那么vector可能是比列表和deque更好的容器选择。每次向List中插入一个对象时,List都会在堆上分配一个节点,而vector会将所有对象存储在堆上的一个连续内存区域中。

如果你有:

std::vector<Dog> dogs;
std::vector<Cat> cats;
std::vector<Animal*> animals;
void addDog(Dog& dog, std::vector<Dog>& dogs, std::vector<Animal*>& animals) {
  dogs.push_back(dog);
  animals.push_back(&dog);
}

所有的狗和猫被存储在堆上两个连续的内存区域。

对于包含每种情况所需的超集数据的联合,您可能可以使用一个简单的包装器类来做一些事情。这将包含一个指向包含不同行为代码的共享策略对象的指针。因此,猫是类PolyAnimal的对象,其speciesName = "cat",还有一个捕食者喂食策略,等等。

可能解决潜在问题的更好方法是为更自然的设计设置适当的自定义分配器。

如果在编译时已知所有可能的子类(这种情况也称为"闭集多态"),则建议使用变体(无论是Boost的还是自cxx17以来的STL)。

必须使用访问者模式来访问对象的接口有点烦人(在我看来),这就是为什么我写了一个暴露基类接口(甚至基类型操作符)的变体类型。可以在https://github.com/Krzmbrzl/polymorphic_variant(需要c++ 17,因为它是围绕std::variant构建的)。

克隆@Draziv:

类定义

class Animal {
public:
    virtual void eat() = 0;
};
class Cat : public Animal {
    virtual void eat() final override {
        std::cout << "Mmh, tasty fish!" << std::endl;
    }
};
class Dog: public Animal {
    virtual void eat() final override {
        std::cout << "Mmh, tasty bone!" << std::endl;
    }
};

pv::polymorphic_variant< Animal, Dog, Cat > variant;
variant->eat();
variant = Cat{};
variant->eat();
输出:

Mmh, tasty bone!
Mmh, tasty fish!