创建一个子类的多个实例,只有一个超类的实例

Posted

技术标签:

【中文标题】创建一个子类的多个实例,只有一个超类的实例【英文标题】:Creating multiple instances of a subclass, with only one instance of a superclass 【发布时间】:2020-04-20 11:33:02 【问题描述】:

我正在尝试实现享元模式,但我不太确定继承是如何工作的,所以我不太确定这种模式是如何工作的。

假设我有一个超类,它包含所有“重”信息 - 纹理等,内在信息(永远不会改变的信息)。

class Block
public: //for the sake of the example
Texture tex; 
etc.
;

我的灯光类的数据会发生变化:

class Block_light : public Block 
public:
int posX, posY, posZ;
int color;
etc.
;

那么,如果我在 main 中创建 n Block_light,我会同时创建 n Block 还是将它们都绑定到一个实例?

 int main()
 std::vector<Block_light> blocks(n);
 return 0;
 ;

我创建了 n Block_light 和 1 Block 还是 n Block_light 和 n Block?如果是后者,我怎样才能让它只使用一个实例?

【问题讨论】:

在您的示例中,BlockBlock_light 之间没有关系。如果Block_light 继承自Block,您将创建n 是的,一个错字。那么,我该如何让它只创建 1 个区块? 由于不能继承。每个Block_light 对象也是一个 Block 对象。 不要让它继承。继承将使子类足够大以包含超类的对象。在Block_light 中可能应该有一个引用/指针/shared_ptrBlock。什么最合适在很大程度上取决于您的用例。 (注意静态变量,由于隐式线程检查,它们会产生运行时开销) 【参考方案1】:

C++ 对象模型确保子类(即派生 类)contains 的每个实例也是其所有超类(即 类)的自己的实例。

在您的情况下,这意味着每个Block_light 对象都将具有所有Block_light 属性,以及具有所有Block 属性的Block 子对象。换句话说,Block_light 一点也不轻。

如果你想共享一个共同的状态:

您可以 use composition instead of inheritance:Block_light 不会继承自 Block,而是引用共享的 Block 对象。 如果您仍然需要能够交替使用 BlockBlock_light,则可以使两者都继承自一个通用接口(即只有虚函数而没有状态的 cls)IBlock。 您也可以使用flyweight pattern,其中部分状态(共享与否)被“外部化”为外部状态。这种模式的特殊之处在于,外部状态的引用由调用者提供给类函数,因此享元对象不需要存储指向这种状态的指针。

第一个选项如下所示:

class Block ...; 
class Block_light        // no inheritance 
    shared_ptr<Block> b;  // but (private) composition   
    ...
;  

第二个选项是:

class IBlock   // no member variables
public:
    virtual Texture get_texture()=0;
    virtual ~IBlock(); 
;  

class Block : public IBlock   // sharable state
    Texture tex; 
public: 
    Texture get_texture() override  return tex; 
;    

class Block_light : public IBlock 
    shared_ptr<IBlock> b;  // or IBlock or Block depending on the needs.  
public:
    Block_light (shared_ptr<IBlock>i) : b(i) 
    Texture get_texture() override  return b->get_texture(); 
;  

int main()  // just a perfectible quick example
    auto myb=make_shared<Block>();
    Block_light b1(myb);
    b1.get_texture();

最后一个会这样使用:

int main()  // just a perfectible quick example
    Block myb;     // shared part; 
    Block_light bl;
    bl.get_texture(myb);

我不会详细介绍享元实现的细节,但你有an example here。但是,在选择此模式之前请三思,因为提供共享上下文可能具有挑战性且容易出错。

【讨论】:

FWIW 第一个链接(“包含”)需要身份验证才能访问。我不再有费米的信用了。 @DaveNewton 感谢您通知我此更改。这篇文章有一些有趣的模式来可视化这些原则。我已更新链接以使用回溯机器指向我回答时的文章。【参考方案2】:

您实现的不是轻量级模式。没有 Block_light 这样的东西,而是 Block 将是由 Block1 之类的东西提供的实现的纯接口,看起来就像你的 Block 定义的样子。

例如:

#include <string>
#include <unordered_map>
#include <memory>

class BlockState 
public:
    int posX, posY, posZ;
    int color;
;

struct Texture 
    Texture(const std::string &);
    char data[8196];
;

class Block 
public:
    Block(std::string) ;
    virtual void render(const BlockState &state) const = 0;
;

class Block1 final : public Block 
public:
    Block1(std::string name) : Block(name), tex(name) ;
    void render(const BlockState &state) const override;
private:
    const Texture tex;
;

class BlockFactory final 
public:
    std::shared_ptr<const Block> getBlock(const std::string &key) 
        auto it = dict.find(key);
        if(it != dict.end()) return it->second;
        auto it2 = dict.try_emplace(key, std::make_shared<Block1>(key));
        return it2.first->second;
    
private:
    std::unordered_map<std::string, std::shared_ptr<const Block>> dict;
;

void render () 
    static BlockFactory factory;
    BlockState a, b;
    factory.getBlock("brick")->render(a);
    factory.getBlock("brick")->render(b);

BlockFactory 正在为您管理Block 的实例,由不可见的Block1 实现实现。使用不同的工厂进行测试等,您可以根据需要切换下面的实现以进行测试。

您的“每个实例”状态不是从 Block 继承的,而是按值单独保存,其中 Block 实例由 BlockFactory 拥有,并且仅通过引用/指针传递。

【讨论】:

以上是关于创建一个子类的多个实例,只有一个超类的实例的主要内容,如果未能解决你的问题,请参考以下文章

如何使子类只能从实例访问?

当子类被实例化时,超类的私有成员是不是也被实例化? [复制]

在抽象超类的静态方法中创建子类的实例? [复制]

选择题

PHP在单例模式中,是多个线程只有一个实例还是一个线程只有一个实例

在 C++ 中访问一个类的多个实例