简单的对象关系

部分于

第一种简单的对象关系称为“部分于”,也就是说 “B 是 A 的一部分”。这种关系是非常容易处理的,只需将 B 作为 A 的成员即可。比如:数学上分子和分母是分数的一部分,所以如果要设计分数类 Fraction,则可以:

class Fraction {
private:
    int numerator;   // 分子
    int denominator; // 分母

public:
    Fraction(int numerator, int denominator) :
    numerator{numerator}, denominator{denominator} {
        reduce(); // 约分
    }
    // [...]
};

像这样,分子和分母作为分数类的成员而出现。又比如,一个学生拥有它的学号和姓名,那么就把它们放到成员列表里即可:

class Student {
private:
    int number;
    std::string name;

public:
    std::string getName() { return name; }
};

下一种关系叫做“有”,也就是说“A 拥有 B”。这种关系最简单的处理方式是将指向 B 的指针作为 A 的成员。比如:一个班级拥有三十个学生,那么设计班级类的时候需要这样:

class Student;
class MyClass {
private:
    Student* students[30]{};

public:
    Student& getStudent(int no) {
        return students[no];
    }

    // [...]
};

再比如一个人可以养最多三条狗,那么人和狗的关系则会这样实现:

class Dog;
class Host {
private:
    Dog* dogs[3]{};
    int num;

public:
    void addDog(const Dog& d) {
        if (num == 3) std::cout << "Already 3 dogs!" << std::endl;
        else dogs[num++] = &d;
    }
};

那肯定会有人问,“有”和“部分于”这两种关系感觉没什么区别啊?为什么一个就存放对象本身而另外一个却存放指向对象的指针呢?这是因为两种关系的区别在于某个确定的 A 是否只属于一个 B。在“部分于”的例子中,A 总是归这一个 B 所“管”的:分子和分母都是这个分数的部分,而不能是其它分数的部分;学生的学号和姓名只属于它本人,而不能属于别人。但在学生-班级的示例中,一个学生可能会同时属于多个班级(小学班级、中学班级;或者分层教学之类的),甚至不属于任何一个班级;在主人和狗的示例中,一条狗可能会被多个主人“共同管理”,或者是条流浪狗。

从更形象的方面讲,“部分于”更注重于 B 单独的使用。我们创建分数的时候不可能只创建分子或者只创建分母,我们总是创建一个整体的分数。但是“有”的关系让 A 的使用更加“独立”。我们可以先创建一个 A,然后让 B 的一个成员指向它。当 A 离开了 B(这个“有”的关系发生了变动),那么只需要改变指针的指向即可,而不需要再对已经创建好的 B 做任何操作。

从更现代的角度讲,应该使用 std::reference_wrapper 来存放对象的引用而非使用指针。但是 std::reference_wrapper 的使用我觉得现在讲有点太早了。

使用

所谓“使用”关系就是说 B 能用到 A (或 A 的某个部分)做事情。这种关系的处理方式实际上和“有”是一样的:B 存储指向 A 的指针。比如士兵可以用它的武器,那么:

class Armament;
class Soldier {
private:
    Armament* arm;
};

它和“有”的区别就是使用的关系可以是双向的。就刚才的例子,你也可以说武器通过士兵来攻击——某种意义上,这个武器也“使用”了士兵(你总不能说士兵是武器的一部分吧):

class Armament;
class Soldier {
private:
    Armament* arm{};
    void setArmament(Armament& a);
};
class Armament {
private:
    const Soldier* holder{};
public:
    void setHolder(const Soldier& s) {
        holder = &s;
    }
};
void Soldier::setArmament(Armament& a) {
    arm = &a;
    a.setHolder(*this);
}

于是你可以在武器类中也设置指向其使用者的指针。这样,两者之间可以通过指针相互访问其内容,这就是“使用”的实现了。

这种情形下你是必须使用指针的。如果两方面都存储对象本身而非指针,那么就会出现 Soldier 类中有 Armament 成员,而 Armament 又有 Soldier 成员……这样套娃的事情显然是错误的。

上面的几个例子都用到了不带定义的类声明。严谨地讲,这种声明被称为前置声明(Forward declaration),前置声明在其所引入的类名被定义前都是不完整类型。可以使用指向不完整类型的指针或绑定引用到其上,但不能定义其实例或者访问其成员。换而言之,不完整类型禁止一切需要其大小或者布局的操作。比如最后一个例子中,从行 1 引入 Armament 不完整类型直至行 7 出现其定义为止,都不能直接定义 Armament 类的对象,或者访问其成员(如 Armament* p; p->holder; 是禁止的)。

最近更新:
代码未运行