【问题标题】:How do I create a class with reference member and vector of pointers?如何创建具有引用成员和指针向量的类?
【发布时间】:2021-11-13 18:17:03
【问题描述】:

我对 C++ 相当陌生,并且正在从事一个个人项目。我想在 C++ 中创建一个 vector<Entity*> entitities,其中每个 Entity 对象都是唯一的。我在标题Entity.h 中有一个要创建的类。现在 Entity 有两个成员变量:

  1. Rectangle rect - 一个 Rectangle 类型的对象,它有四个浮点变量作为成员变量(x1、y1、x2、y2),它们是矩形两个对角的坐标
  2. vector<Component*> - 基类类型Component 的许多不同组件以及一些派生类。 Class Component 的代码如下所示:
/*************************** BASE CLASS **************************/
class Component
{
public:
    virtual ~Component() = default;
    virtual Component* Clone() = 0;
};

/*************************** DERIVED CLASS 1 **************************/
class BasicComponent: public Component
{
private:
    int B= 0;
public:
    BasicComponent* Clone() override {
        return new BasicComponent(B);
    }

    BasicComponent(const int& mB) :B(mB){}
    BasicComponent() :B(0) {}

};

/*************************** DERIVED CLASS 2 **************************/
class AdvancedComponent: public Component
{
private:
    float A = 0.f;
    int iA = 0;
public:
    AdvancedComponent* Clone() override {
        return new AdvancedComponent(A, iA);
    }

    AdvancedComponent(const float& mA, const int& miA) :A(mA),iA(miA) {}
    AdvancedComponent() :A(0.f),iA(0) {}

};

由于我希望实体向量中的每个Entity 都是唯一的,即拥有自己的矩形和组件,我应该如何创建类?

我的问题是,class Entity 应该是什么样子?我应该为这个类创建单独的 CopyConstructor、Assignment Constructor 和 Destructor 吗?另外,如果我想实现将一个实体复制到另一个实体(深度复制),是否有必要拥有全部 3 个(复制、赋值和析构函数)?

【问题讨论】:

  • 你给Entity的成员了,对吧?是什么阻止您编写课程?
  • 为什么vector<Entity*> 相关?只是说每个Entity 都必须有自己的RectangleComponents 的副本,这还不够吗?
  • @RahulPillai:C# 指针;它只是没有语法。
  • @RahulPillai “使用vector<Entity*> 而不是vector<Entity> 有错吗?” -- 这不是我想要的。您在询问您的代码;我说的是你的问题。更简单的问题分心更少,因此更有可能得到回答而不会迷失方向。如果您从您的问题中删除所有提及向量并仅声明每个Entity 必须有自己的RectangleComponents,会丢失什么? (强调向量而不是要求,看起来好像不在向量中的对象必须共享矩形和组件。)
  • 使用动态分配的原因有很多,但如果你真的想要动态生命周期,那么原则上你不需要它。但是,如果您想要运行时多态性(正如您的组件子类所示),没有间接性就很难做到。

标签: c++ c++11 entity-component-system


【解决方案1】:

我的问题是,Entity 类应该是什么样的?我应该为这个类创建单独的 CopyConstructor、Assignment Constructor 和 Destructor 吗?另外,如果我想实现将一个实体复制到另一个实体(深度复制),是否有必要拥有全部 3 个(复制、赋值和析构函数)?

这个问题的答案并不真正取决于Entity 看起来像什么,而是取决于它打算具有什么语义。

您说每个Entity 都是“唯一的”,但C++ 中的每个对象(甚至每个int,即使它们碰巧具有相同的值)在技术上都是唯一的。那么你的真正意思是什么?

  1. Entity 应该是可复制的吗?复制构造 Entity 意味着两个 Entity 对象具有相同的内容(字面意思是组件指针是浅拷贝的,逻辑上是深拷贝的)。

    如果不是,您可能不想编写(或可能明确地delete)复制构造函数和/或复制赋值运算符。

  2. Entity 应该可以移动吗?可能是的,因为它不违反唯一性并且使它们更容易有效地使用。

    如果是这样,您应该确保它具有移动构造函数和移动赋值运算符(通过编写它或安排编译器生成有用的默认值)。

另外,如果我想实现将一个实体复制到另一个实体(深度复制)

这似乎违反了您的唯一性约束,但是是的,您需要一个复制构造函数和复制赋值运算符。

但是,最佳做法是避免将资源管理与程序逻辑交错。因此,与其编写所有这些,不如考虑让智能指针为您自动化它。请参阅this answer 中提到的零规则进行比较。

事实上,我们可以用很少的代码说明所有合理的语义:

template <typename ComponentPtr>
struct ZeroEntity
{
  Rectangle bound;
  std::vector<ComponentPtr> components;
};

using ShallowCopyZeroEntity = ZeroEntity<std::shared_ptr<Component>>;
using NoCopyOnlyMoveEntity = ZeroEntity<std::unique_ptr<Component>>;
using DeepCopyZeroEntity = ZeroEntity<my::clone_ptr<Component>>;

除了我们还需要写一个深拷贝clone_ptr,类似

namespace my {
template <typename T>
class clone_ptr
{
  std::unique_ptr<T> p_;

  std::unique_ptr<T> clone() const { return std::unique_ptr<T>{p_ ? p_->Clone() : nullptr}; }

public:

  using pointer = typename std::unique_ptr<T>::pointer;

  explicit clone_ptr(pointer p) : p_(p) {}

  // copy behaviour is where the cloning happens
  clone_ptr(clone_ptr const& other) : p_(other.clone()) {}
  clone_ptr& operator=(clone_ptr other)
  {
    other.swap(*this);
  }

  // move behaviour (and destructor) generated by unique_ptr
  clone_ptr(clone_ptr&& other) = default;
  clone_ptr& operator=(clone_ptr&&) = default;

  // now write all the same swap, release, reset, operator* etc. as std::unique_ptr
};
}

...如果这看起来很多,想象一下它会与您的 Entity 代码交错而不是像这样收集到一个地方会有多混乱。

【讨论】:

  • 非常感谢!我刚刚习惯了智能指针的想法,所以我没有在我的解决方案中使用它。这个项目对我来说更像是一次学习经历。我尝试在没有指针的情况下实现解决方案,如下所示。我纠正自己说 Entity 是独一无二的。我认为更好的说法是它们在 vector 和不同的 Rectangle 中可能有也可能没有多个组件。这并没有真正使它们独一无二,因为我仍然需要启用一些能够克隆它们的系统。如果我的解决方案可能导致内存泄漏,请告诉我。
【解决方案2】:

主要目的只是启用实体的复制。这行得通吗?

#include "Components.h"
#include "Rectangle.h"
#include <vector>
using namespace std;
class Entity
{

public:
    Rectangle& Box;
    vector<Component*>& Components;

    //Default constructor call
    Entity(): Box(*new Rectangle), Components(*new vector<Component*>){}
 
    //Constructor call with parameters
    Entity(Rectangle& B, vector<Component*>& C) : Box(B), Components(C){}

    //Copy constructor that makes Entity copyable
    Entity(const Entity& E) : Box(*new Rectangle(E.Box)), Components(*new vector<Component*>(E.Components.size())) {
     
 
        for (unsigned int i = 0; i < E.Components.size(); i++) {
            Components[i] = E.Components[i]->Clone();
        }
    }

    //Assignment operator constructor
    Entity& operator=(const Entity& E){ 
        //Entity* pE = &E;
        if (this != &E)
        {
            delete& Box;
            delete& Components;
            Box = *new Rectangle(E.Box);
            Components = *new vector<Component*>(E.Components.size());
            for (unsigned int i = 0; i < E.Components.size(); i++) {
                Components[i] = E.Components[i]->Clone();
            }
        }
         return *this;

    }

};

【讨论】:

  • 首先确定事物的语义。 Components 向量是否应该与实体一样长?然后让它成为普通会员。您不需要动态分配单个向量。
  • 如果您确实必须动态分配某些东西,不要只存储&amp; 引用。它们不用于表示所有权,并且原始指针至少不会隐藏代码气味。首先首选自动生命周期,如果必须动态分配,请使用智能指针。拥有原始指针(您必须手动 delete)是一个遥远的第三选择,拥有引用根本不会出现在列表中。
  • 克隆本身会起作用(如果不优雅),但是您的 operator= 的异常安全性为零:如果 newClone 调用之一引发异常,则您已经丢弃您分配给的左侧Entity,现在无法使用。先写一个析构函数,然后写一个不抛出的swap(或移动赋值),再写一个普通的拷贝赋值,拷贝构造一个临时的,然后用*this交换它。
  • 谢谢。这更有意义!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
  • 2021-12-18
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
相关资源
最近更新 更多