函数中两个类之间的C ++交互

时间:2019-07-09 13:58:13

标签: c++ class

我试图在这里做一个相对简单的任务,不确定如何去做。

我有两类兴趣爱好ParentChild。它们都是“人类”类的特例。我是这样创建的。

main.cpp 的相关位:

int main() {
    //create an object of class Parent parent1 with a name Albert.
    Parent parent1("Albert");

    //create an object of a class Child child1 named John.
    Child child1("John");
    return 0;
}

Parent.h 标头包含一个名为addChild()的函数。它所做的只是将一个子代添加到Parent对象内的Children的向量中。

Parent.h 的相关位:

class Parent: public Human {
public:
    vector<Human> Children;

   ...
void addChild(Human aHuman){
        Children.push_back(aHuman);
    }

  };

我想要实现的是addChild()函数同时向该孩子添加一个父对象。也就是说,要做类似的事情:

void addChild(Human aHuman) {
        Children.push_back(aHuman);
        aHuman.Parent = MYSELF;
    }

以上,MYSELF表示我不知道如何编码的那一点。

所以当我运行它时:

int main() {
    //create an object of class Parent parent1 with a name Albert.
    Parent parent1("Albert"); 

    //create an object of a class Child child1 named John.
    Child child1("John");

    parent1.addChild(child1);

    return 0;
}

我希望将艾伯特设为约翰的父母,并将约翰设为艾伯特的孩子。

有人可以帮我吗?

编辑:

根据Jan的建议的代码:

#include <iostream>
#include<vector>

class Human{

};

class Child;

class Parent : public Human
{
    std::vector<Child> children;
public:
    friend Child;
    void addChild(Child& child)
    {
        children.emplace_back(child);
        child.parents.emplace_back(*this);
    };
};

class Child : public Human
{
    std::vector<Parent> parents;
public:
    friend Parent;
    void addParent(Parent& parent)
    {
        parents.emplace_back(parent);
        parent.children.emplace_back(*this);
    }
    ;
};

错误:

In member function 'void Parent::addChild(Child&)':
invalid use of incomplete type 'class Child'
         child.parents.emplace_back(*this);
error: forward declaration of 'class Child'
 class Child;

4 个答案:

答案 0 :(得分:3)

我担心您会遇到XY问题,并且通常将对象多态性和C ++设计的重要概念混合在一起,但是,这是您所要求的解决方案:

#include <vector>
#include <memory>

struct Human {
    virtual ~Human() = default;
};

struct Parent;

struct Child: Human{
    Child(std::string) {}
    void addParent(std::weak_ptr<Parent> aParent) {
        Parent = aParent;
    }
private:
    std::weak_ptr<Parent> Parent;
};

struct Parent: Human, std::enable_shared_from_this<Parent> {
    Parent(std::string) {}
    void addChild(Child aChild){
        aChild.addParent(shared_from_this());
        Children.push_back(aChild);

    }
private:
    std::vector<Child> Children;
};



int main() {
    auto parent1 = std::make_shared<Parent>("Albert"); //create an object of class Parent parent1 with a name Albert.
    Child child1("John");//create an object of a class Child child1 named John.
    parent1->addChild(child1);
}

这不是很漂亮,但这更多的是您设计中的问题,即问题。

请注意,对于Human类的按值,复制,生存期和不相关的所有调用,此代码仍远非最佳。但是,更好的解决方案将需要更多的上下文和设计。

更新时更新 更新Jahns代码 Jahns代码从C ++ 11到C ++ 14无效: 正如您所问,jans代码有什么问题:它无法编译,因为它通常无法正常工作。由于std::vector<T>需要完整类型,因此基本上等同于存储T的副本。 从C ++ 17开始: Jahns代码应该可以运行,但是因为现在std::vector<T>也可以用作存储对T的引用,所以基本上等同于下面描述的技巧。

由于您的设计,您有2个具有循环依赖关系的类:子女应保存其父母,父母应保存其子女。因此,您就是Jan决定将孩子的父母副本保存在孩子的父母中。但是为了保存它,计算机必须知道它有多大。

因此,孩子必须知道父母有多大才能知道自己将有多大,但是要知道父母有多大,就必须首先知道自身有多大,因为它也保存在父母中。看到问题了吗?自我参照是魔鬼的游乐场,通常会导致无限递归。

然而,打破周期的一个窍门是,对孩子说:“不要保存整个父母,只是对其进行引用,因此您不必知道它有多大。”这是通过父级的正向减速以及智能指针的使用来完成的,这些使您免于做坏事,例如尝试使用空白引用。 (那样的话,像Parent*这样的原始指针就不好了,不要 如果不使用它们,您可能会得到未定义的行为,即星期五2次函数调用的seg错误)

另一个非常相似的通用技巧是,孩子和父母在同一根上达成共识,就像人类的基层阶级一样。它还通过使用引用解决了该问题。您的班级建议您首先尝试,否则您的老师敦促您采用这种方式。

但是所有这些技巧都增加了复杂性,降低了性能和安全性,因为您将工作从编译时间推到了运行时。 Java,C#,Python和Co.甚至默认情况下都这样做。使反模式更常见,恕我直言。

我只能重复:我建议读一本有关C ++的书,Lipman撰写的C ++ Primer是我作为初学者阅读的书。

答案 1 :(得分:2)

主要有2个问题:

  • 您按值传递而不是引用传递,因此您只能修改副本
  • 您可以进行对象切片:std::vector<Human>仅存储Parent / Child的人类部分。

答案 2 :(得分:0)

在不使用&的情况下使用void addChild(Human aHuman)时,您会遇到问题,如果在不使用&的情况下将保存在对象中的内容更改为0,则需要执行void addChild(Human&aHuman)。功能,当您插入addChild(child1); MYSELF可以是这个,但是您需要更改向量而不是获取对象

答案 3 :(得分:0)

您遇到了新手OOP程序员的一个常见问题:您尝试通过扩展OOP类而不是创建关系类来解决关系问题。

新手方法

首先,让我们继续进行一下操作……当然,每个Parent都有一个Child,每个Child都有一个Parent,自然似乎将这些关系存储在每个类中。因此:

class Parent : public Human
{
    std::vector<Child> children;
  public:
    void addChild(const Child& child);
};

class Child : public Human
{
    std::vector<Parent> parents;
  public:
    void addParent(const Parent& parent);
};

现在,让我们实现add...函数:

void Child::addParent(const Parent& parent)
{
    parents.emplace_back(parent);
}
void Parent::addChild(const Child& child)
{
    children.emplace_back(child);
}

正如您已经提到的,这要求您分别更新父级和子级。因此,让我们改变方法,让班级成为朋友:

class Child;
class Parent : public Human
{
    std::vector<Child> children;
  public:
    friend Child;
    void addChild(Child& child);
};

class Child : public Human
{
    std::vector<Parent> parents;
  public:
    friend Parent;
    void addParent(Parent& parent);
};

此朋友关系现在也可以用于更新其他类:

void Child::addParent(Parent& parent)
{
    parents.emplace_back(parent);
    parent.addChild(*this);
}
void Parent::addChild(Child& child)
{
    children.emplace_back(child);
    child.addParent(*this);
}

该死,这行不通!发生了什么?我们有效地创建了一个无限循环。因为在Child上添加Parent会添加子项,但还会在子项上调用addParent函数,后者会添加父项,但还必须调用addChild父母的功能,这会增加一个孩子,...你明白了。

我们该如何解决?一种方法是添加子级/父级:

void Child::addParent(Parent& parent)
{
    parents.emplace_back(parent);
    parent.children.emplace_back(*this);
}
void Parent::addChild(Child& child)
{
    children.emplace_back(child);
    child.parents.emplace_back(*this);
}

这可行,但是我们必须为此付出沉重的代价。 ParentChild必须知道彼此的内部结构才能正常工作。这意味着,您不能轻易更改这些类,并且用户很难扩展它们。 (他们需要研究整个班级的内部知识,以确保它能正常工作。)

此外,这种方法还有其他一些问题:

  • 如果我们在addParentaddChild函数中更改行顺序会怎样? (提示:我们保存了该对象的副本,但未添加适当的元素。)
  • 如何从父母中删除孩子并正确更新孩子的父母?
  • 作为父母,您如何确定孩子不会以怪异的方式改变您的内部行为,反之亦然?

此外,我们现在正在使用类内部对象的副本。这意味着,更新父级将使子级内部的实际数据保持不变(反之亦然)。这可以通过使用指针部分解决,但也意味着您必须开始管理此简单任务的指针...

方法方法

那么,什么是合适的解决方案?一个简单的选项是处理这些相互关联的更新的功能。采用第一个实现(不更新父级和子级),我们可以使用以下功能:

void linkChildToParent(Parent& parent, Child& child)
{
    parent.addChild(child);
    child.addChild(parent);
}

linkChildToParent函数...

  • 将正确更新您的孩子和父母。
  • 不依赖于行顺序。
  • 易于阅读和维护。
  • ChildParent的用户很容易扩展。

这种方法解决了我们的大多数问题,甚至可以扩展为正确,轻松地处理指针。但是,我们仍然需要进行一些工作来更新合适的成员,...如何解决?当然,按照适当的OOP!

面向对象的方法

我们遇到的所有问题都与我们存储父母与子女之间的关系的方式有关。这表明我们的抽象是错误的!考虑一下,我们可以提出正确的抽象,一个ParentalRelation类:

//Given, that we only have basic functionality, a struct is sufficient here...
struct ParentalRelation
{
    Child child;
    Parent parent;
};

这使我们可以将父子信息存储在单独的表中。此外:

  • ChildParent不需要对它们的内部知识一无所知。
  • 扩展任何类都很容易。
  • 删除或更改父母/子女非常容易。
  • 坦率地说,要理解和编写的代码要少得多。

此外,由于只需ParentalRelation即可管理指针,因此很容易切换到指针。这样的优点是,在更改父或子时,始终具有正确的信息,而无需其他更新代码。

免责声明

  • 我希望这很清楚。
  • 虽然在这里看起来很容易,但是在实践中找到ParentalRelation方法可能非常困难。