I'm writing a class that acts as the base for almost all objects in my project. I know this class technically doesn't represent a tree structure, because it can have more than 2 children. What should I change? Should I be using smart pointers? I'm a self-taught C++ programmer, so I've got much more to learn.
Here's the declaration:
class Object
{
public:
Object();
Object(const std::string& name);
Object(Object* const parent);
Object(const std::string& name, Object* const parent);
public:
virtual ~Object();
public:
virtual void AddChild(Object* const obj) final;
virtual void RemoveChild(Object* const obj) final;
virtual std::vector<Object*> FindChildren(const std::string& name, const bool recursive = false) final;
virtual Object* FindFirstChild(const std::string& name, const bool recursive = false) final;
virtual Object* FindLastChild(const std::string& name, const bool recursive = false) final;
virtual std::vector<std::string> ChildrenNames() const final;
virtual bool HasChild(Object* const obj) const final;
public:
virtual Object* GetRoot() final;
public:
virtual bool IsRoot() const final;
virtual bool IsParent() const final;
public:
virtual const std::string& GetName() const final;
virtual void SetName(const std::string& name) final;
virtual Object* GetParent() const final;
protected:
std::string name = unnamed_object;
protected:
Object* parent;
std::vector<Object*> children;
};
Here's the definitions:
Object::Object() : Object("", nullptr) {}
Object::Object(const std::string& name) : Object(name, nullptr) {}
Object::Object(Object* const parent) : Object("", parent) {}
Object::Object(const std::string& name, Object* const parent)
{
if(!name.empty()) this->name = name;
if(parent != nullptr) parent->AddChild(this);
}
Object::~Object()
{
for(auto& child : children) delete child;
children.clear();
if(parent != nullptr) parent->children.erase(std::find(parent->children.begin(), parent->children.end(), this));
}
void Object::AddChild(Object* const obj)
{
if(obj == nullptr) throw std::invalid_argument("The object cannot be null.");
if(HasChild(obj)) throw std::invalid_argument("The object is already a child.");
obj->parent = this;
children.push_back(obj);
}
void Object::RemoveChild(Object* const obj)
{
if(obj == nullptr) throw std::invalid_argument("The object cannot be null.");
auto iter = std::find(children.begin(), children.end(), obj);
if(iter == children.end()) throw std::invalid_argument("The object is not a child.");
obj->parent = nullptr;
children.erase(iter);
}
std::vector<Object*> Object::FindChildren(const std::string& name, const bool recursive)
{
std::vector<Object*> result;
for(auto& child : children)
{
if(name == child->name) result.push_back(child);
if(recursive)
{
if(child->IsParent())
{
std::vector<Object*> resultRecursive = child->FindChildren(name, true);
result.insert(result.end(), resultRecursive.begin(), resultRecursive.end());
}
}
}
return result;
}
Object* Object::FindFirstChild(const std::string& name, const bool recursive)
{
for(auto& child : children)
{
if(name == child->name) return child;
if(recursive)
{
if(child->IsParent()) return child->FindFirstChild(name, true);
}
}
return nullptr;
}
Object* Object::FindLastChild(const std::string& name, const bool recursive)
{
const std::vector<Object*> children = FindChildren(name, recursive);
if(children.size() > 0)
{
return children.at(children.size() - 1);
} else {
return nullptr;
}
}
std::vector<std::string> Object::ChildrenNames() const
{
std::vector<std::string> result;
for(auto& child : children) result.push_back(child->name);
return result;
}
bool Object::HasChild(Object* const obj) const
{
if(obj == nullptr) throw std::invalid_argument("The object cannot be null.");
return (std::find(children.begin(), children.end(), obj) != children.end());
}
Object* Object::GetRoot()
{
if(parent != nullptr)
{
return parent->GetRoot();
} else {
return this;
}
}
bool Object::IsRoot() const { return (parent == nullptr); }
bool Object::IsParent() const { return (children.size() > 0); }
const std::string& Object::GetName() const { return name; }
void Object::SetName(const std::string& name)
{
if(!name.empty())
{
this->name = name;
} else {
this->name = unnamed_object;
}
}
Object* Object::GetParent() const { return parent; }