C++没有指针的多态性

发布于 2024-12-01 20:08:46 字数 487 浏览 1 评论 0原文

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

假设我有一个带有虚函数的基类 Animal 和一些派生类(CatDog 等)。真正的派生类包含 4-8 个字节的数据。我想存储一个 std::list 它实际上包含派生对象的项目。我想避免使用 new 在堆上创建许多小对象。

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

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

  1. 创建 std::deque, std::deque, ...;存储 std::list,其中包含来自 deques 的指针;我使用 std::deque 是因为我认为它对对象块有良好的内存管理;

EDIT: A better title for this would be: polymorphism for large collections of objects without individual heap allocations.

Suppose that I have a base class Animal with virtual functions and some derived classes (Cat, Dog, etc.). The real derived classes contain 4-8 bytes of data. I want to store a std::list<Animal> which actually contains items which are derived objects. I want to avoid the creation of many small objects on the heap using new.

Is there any design pattern which can be used to achieve this?

EDIT: My ideas to implement this

  1. create std::deque<Cat>, std::deque<Dog>, ...; store std::list<Animal*> which contains pointers from the deques; I use the std::deque because I suppose that it has a good memory management with chunks of objects;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

雨夜星沙 2024-12-08 20:08:46

最终,没有。

多态性仅适用于非值类型:引用和指针。由于引用只能绑定一次,因此您无法真正在标准容器中使用它们。这给你留下了指示。

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

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


根据您的编辑:

这是一个糟糕的想法。从 任何地方 处的 std::deque 擦除,但开始或结束将使 std::list 中的每个指针无效code>.

鉴于您的评论,这个想法是可行的。然而,为不同类型的对象提供所有这些不同的内存块似乎违背了继承的全部要点。毕竟,您不能只编写一个新的 Animal 类型并将其放入 std::list 中;你必须为其提供内存管理。

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

Ultimately, no.

Polymorphism only works with non-value types: references and pointers. And since references can only be bound once, you cannot really use them in standard containers. That leaves you with pointers.

You're attacking the problem at the wrong end. If you are concerned about the overhead of allocating lots of small objects (and I'm assuming that this is a legitimate concern. That is, you have actual profiling data or sufficient experience to know it is a concern for your specific application), then you should fix that. Change how you're allocating memory for these objects. Make a small allocation heap or something.

Admittedly, pre-C++0x's allocators are somewhat lacking in this regard, since they have to be stateless. But for your purposes, you should be able to deal with it.


From your edit:

That is a terrible idea. Erasing from a std::deque at anywhere but the start or end will invalidate every pointer in your std::list.

Given your comment, this idea is functional. However, having all of these different memory blocks for different kinds of objects seems to go against the whole point of inheritance. After all, you can't just write a new type of Animal and slip it into the std::list; you have to provide memory management for it.

Are you sure that inheritance-based polymorphism is what you need here? Are you sure that some other methodology would not work just as well?

卖梦商人 2024-12-08 20:08:46

我意识到这个问题已经很老了,但我找到了一个相当不错的解决方案。

假设:

您提前知道所有派生类(根据您的编辑,这是正确的)。

技巧:

使用 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!

I realize that this question is old, but I found a somewhat pretty solution.

Assumption:

You know all the derived classes in advance (given your edit, this is true).

Trick:

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

Example classes:

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;
    }
};

Example variant/visitor:

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;
    }
};

Example usage:

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();
    }
}

Example output

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!
森林散布 2024-12-08 20:08:46

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

如果有:

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);
}

那么所有的狗和猫都存储在堆上两个连续的内存区域中。

If you're worried about allocating many small heap objects, then a vector may be a better choice of container rather than a list and a deque. list will allocate a node on the heap each time that you insert an object into the list, whereas vector will store all objects in a contiguous region of memory on the heap.

If you have:

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);
}

Then all dogs and cats are stored in two contiguous region of memory on the heap.

污味仙女 2024-12-08 20:08:46

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

必须使用访问者模式来访问对象的接口有点烦人(imo),这就是为什么我编写了一个公开基类接口(甚至基类型运算符)的变体类型。可以在以下位置找到:
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!

This is a follow-up on the suggestion to use a variant (be it Boost's or the one from the STL since cxx17), if all possible child-classes are known at compile time (this case is also known as "closed-set polymorphism").

Having to use the visitor pattern to access your object's interface is a bit annoying (imo), which is why I have written a variant-type that exposes the base-class interface (and even the base-type operators). It can be found at
https://github.com/Krzmbrzl/polymorphic_variant (requires C++17 as it is built around std::variant).

Cloning the example from @Draziv:

Class-definitions

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;
    }
};

Example usage

pv::polymorphic_variant< Animal, Dog, Cat > variant;
variant->eat();

variant = Cat{};
variant->eat();

Output:

Mmh, tasty bone!
Mmh, tasty fish!
信愁 2024-12-08 20:08:46

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

解决根本问题的更好方法可能是设置适当的自定义分配器以获得更自然的设计。

You probably could do something with a simple wrapper class for a union containing the super-set of data needed by every case. This would contain a pointer to shared strategy objects that contained the code for the different behaviours. So a cat is an object of class PolyAnimal with speciesName = "cat", a PredatorFeedingStrategy and so on.

Likely a better way of solving the underlying problem is setting up appropriate custom allocators for a more natural design.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文