【问题标题】:Operation private to two classes without friend functions没有友元函数的两个类的私有操作
【发布时间】:2016-07-31 07:40:07
【问题描述】:

考虑两个具有简单一对一关系的类 BoxRabbit - 盒子最多可以包含一只兔子,兔子最多可以坐在一个盒子里。

在伪代码中,我们有如下接口:

class Box
  - getRabbit():Rabbit
  - putRabbit(rabbit:Rabbit)

class Rabbit
  - getContainingBox():Box
  - putIntoBox(box:Box)

当然我们要保持不变:box.getRabbit().getContainingBox() == box,如果box不为空,rabbit也一样。

在 C++ 中,我们可以将函数 putRabbitIntoBox(Rabbit* rabbit, Box* box) 声明为 BoxRabbit 的友元,并根据 putRabbitIntoBox 实现 Box::putRabbitRabbit::putIntoBox。而且我认为这种方法没有任何重大缺点(如果我错了,请纠正我):

class Box;
class Rabbit;
void putRabbitIntoBox(Rabbit* rabbit, Box* box);

class Rabbit {
public:
    Rabbit() :box(nullptr) {}
    Box* getContainingBox() { return box; }
    void putIntoBox(Box* box) { putRabbitIntoBox(this, box); }
private:
    Box* box;
    friend void putRabbitIntoBox(Rabbit* rabbit, Box* box);
};

class Box {
public:
    Box() :rabbit(nullptr) {}
    Rabbit* getRabbit() { return rabbit; }
    void putRabbit(Rabbit* rabbit) { putRabbitIntoBox(rabbit, this); }
private:
    Rabbit* rabbit;
    friend void putRabbitIntoBox(Rabbit* rabbit, Box* box);
};

void putRabbitIntoBox(Rabbit* rabbit, Box* box) {
    if (rabbit && rabbit->box) {
        rabbit->box->rabbit = nullptr;
    }
    if (box && box->rabbit) {
        box->rabbit->box = nullptr;
    }
    if (rabbit) {
        rabbit->box = box;
    }
    if (box) {
        box->rabbit = rabbit;
    }
}

现在我们决定在 Java(或 C#,或任何没有 friend 函数的语言)中实现相同的 Box/Rabbit 事物。

有解决这个问题的惯用方法吗?

我知道可以通过从putIntoBox 调用putRabbit 来解决它,反之亦然,并带有一些保护代码,如下所示:

void Rabbit::putIntoBox(Box* box) {
    if (this->box == box) {
        return;
    }
    if (this->box) {
        Box* oldBox = this->box;
        this->box = nullptr;
        oldBox->putRabbit(nullptr);
    }
    this->box = box;
    if (box) {
        box->putRabbit(this);
    }
}

但这对我来说看起来很可怕。我们有一个非常容易出错的递归“事物”,而不是一个具有明确定义目的的函数。

【问题讨论】:

  • 如果您没有 friend 声明,则提供公共修饰符。
  • 在您的 C++ 示例中,您有三个函数都做同样的事情。你为什么要那个?选择一个(例如Box::putRabbit)并摆脱另外两个。简化界面并回答您的问题。
  • @Nemo,是的,在 C++ 中,我可以通过让 Box::putRabbit 成为 Rabbit 类的朋友来做到这一点,但我仍然没有理由更喜欢 Box::putRabbitRabbit::putIntoBox在这种情况下,这就是单独的第三个功能的原因。但是在 C# 或 Java 中,我无法从 Box 访问 Rabbit 的私有状态。
  • @bku_drytt,使用 public 修饰符我无法强制提到不变量,因此客户端代码最终可以使多个兔子引用同一个框。除非我使用所描述的丑陋递归技巧(然后 putIntoBoxputRabbit 实际上是公共修饰符)。

标签: c++ oop language-agnostic friend


【解决方案1】:

你应该重新考虑你的班级设计。在您的示例中,将 Rabbit 放入 Box 会更改两个类的状态。一般来说,如果可能的话,可变状态是应该避免的。在这种情况下,如果Box 改变它的状态是可以接受的,但为什么Rabbit 也要改变它的状态呢?

在两个类中具有相同的信息也会导致不一致。

因此我只定义:

class Box {
public:
    putRabbit(Rabbit r);
    Rabbit getRabbit();
...
}

Rabbit 类没有任何与Box 相关的方法。如果您将所有盒子都存储在某个容器中,您仍然可以在其中找到 BoxRabbit,并在其中实现如下方法:

Rabbit r;
...
Box b = allBoxes.findRabbitBox(r);

【讨论】:

    【解决方案2】:

    您的限制是:

    1. 盒子可以连接一只或零只兔子
    2. Rabbit 可以连接一个或零个 Box
    3. 如果 Box 连接到 Rabbit,则 Rabbit 连接到这个 Box,反之亦然
    4. Box 和 Rabbit 都是同一级别的类 - 您不想将其中一个设置为另一个的所有者...

    所以 - 只需在 Box 和 Rabbit 中实现这些约束。第一件事 - 忘记为一切定义 setter 和 getter - 只需实现您需要的功能 - 仅此而已。

    所以 - 让我们定义接口:

    class Box;
    class Rabbit {
    public:
        ~Rabbit() { disconnect(); }
        void connect(Box* box);
        Box* getMyBox() const { return box; }
    private:
        void disconnect();
        Box* box = nullptr;
    };
    class Box {
    public:
        ~Box() { disconnect(); }
        void connect(Rabbit* otherRabbit);
        Rabbit* getMyRabbit() const { return rabbit; }
    private:
        void disconnect();
        Rabbit* rabbit = nullptr;
    };
    

    如您所见 - 通过在每个类中定义一个指向其配对类的指针来满足前两个约束。

    Sp 唯一的左侧约束是确保两个对象(Rabbit 和 Box)彼此连接 - 或根本不连接:

    inline void Rabbit::connect(Box* otherBox)
    {
        if (box != otherBox)
        {
            disconnect();
            box = otherBox;
            if (box != nullptr)
                box->connect(this);
        }
    }
    
    inline void Rabbit::disconnect()
    {
        if (box != nullptr)
        {
            Box* oldBox = box;
            box = nullptr; // do this before calling disconnect on box - otherwise you get infinite recursion...
            oldBox->connect(nullptr);
        }
    }
    

    您必须在Box 中定义类似的函数。 ideone上的工作示例

    在 C++ 中,您可以使用模板 - 因为如您所见 - 两个类的定义方式非常相似...虽然我不能说模板是否可以解决其他语言中的相同问题...

    【讨论】:

      猜你喜欢
      • 2014-04-22
      • 1970-01-01
      • 2014-03-31
      • 1970-01-01
      • 2011-01-09
      • 2013-02-18
      • 1970-01-01
      • 1970-01-01
      • 2012-11-20
      相关资源
      最近更新 更多