C++ 继承与聚合以及;has-a“;vs";is-a";。

C++ 继承与聚合以及;has-a“;vs";is-a";。,c++,design-patterns,inheritance,multiple-inheritance,C++,Design Patterns,Inheritance,Multiple Inheritance,在我的代码中,我发现使用类似mixin的继承来组合具有不同块的对象很有用。我有: class Name { public: typedef int32_t value_type; public: // ctors and dtors void set_value(value_type value) { value_ = value; } const value_type& value() const { return value_; } private

在我的代码中,我发现使用类似mixin的继承来组合具有不同块的对象很有用。我有:

class Name
{
public:
    typedef int32_t value_type;

public:
    // ctors and dtors
    void set_value(value_type value) { value_ = value; }
    const value_type& value() const { return value_; }

private:
    value_type value_;
};

class NamedObject
{
public:
    void set_name(const Name& name) { name_ = name; }
    const Name& name() const { return name_; }

protected:
    // ctors and dtors

private:
    Name name_;
};
我使用此类基类为对象提供具有预定义非虚拟功能的属性:

class MyObject: public NamedObject, public HasZlevel {
  // functionality that is not connected with NamedObject and HasZLevel
};
因此我决定将MyObject视为“is-a”NamedObject,而不是“has-a”名称。 Z-level和Name是在MyObject实例的生存期内永远不会更改的属性。与聚合相比,我更喜欢这种方式,因为在仅为NamedObject或具有HasZLevel接口的对象定义的算法中使用了简化,我可以通过NamedObject*或HasZLevel*传递它们,并确保它们不会由于受保护的DTOR和CTOR而被删除或追加

另一个选项是聚合:

class MyObject
{
public:
    MyObject(const Name& name): name_(name) {}
    void set_name(const Name& name) { named_->set_name(name) }

    // and so on...

private:
    Name name_;
};
要使用这个,我需要一个模板算法,该算法要求from参数类型具有set_name成员函数

在我的情况下,有什么好的理由放弃我的mixin式设计并使用聚合吗?可能在长期维护和修改中?

因为

这就是我喜欢管道胶带的原因 程序员。有时候,你在一个 团队,而你正忙于完成任务 密码,然后有人来找你 桌子,手里拿着咖啡杯,然后开始 喋喋不休地说如果你使用 多线程COM公寓,您的 该应用程序将有34%的亮点,而且 没那么难,因为他 写了一堆模板,所有的 你要做的就是继承 从他的17个模板中,每个模板 平均4个论点,而你 几乎不需要写下 功能。它只是一个巨大的 来自的多重继承列表 不同的班级,嘿,普雷斯托, 多单元线程COM。还有你的 眼睛在游动,你没有眼睛 他妈的知道这该死的东西是什么 我在说,但他就是不去 走开,即使他真的走了, 他正要回办公室 写更多他聪明的课程 完全由多个 从模板继承,不使用 单一的执行机构,以及 它会像疯了一样崩溃 你会在晚上被传呼到 进来试试看 因为他会在某个该死的地方 “设计模式”会议

管道磁带程序员不怕说,“多重继承太糟糕了,停下来,停下来。”

长话短说就是:这真的能帮助你更快地发布你的代码吗?或者,当某个被重写的成员函数阻塞继承树上的某个其他成员时,您将调试多个继承类,并在调试器中单步执行一个复杂的超类节点,这会花费更多的时间吗


是的,我确实写了“被一个会员噎住了”。多重继承真的很糟糕。

问题是,如果你像在这里一样公开继承,在某个时候、某个地方,有人会将你的类视为
命名对象
HasZlevel
,并尝试通过父指针删除、传递等等。这可能不会太奏效

如果您想使用这样的mixin类型策略,至少要使用
private
继承,然后
使用所需的父方法进入相应子类的公共部分


然而,我不会特别建议这样做。如果使用继承表示替换,则基类表示接口,而不是功能。我想说在这种情况下使用构图更有意义。整个标准库都是基于通用(模板)算法的,所以我不会去做这方面的工作。独立模板算法方法是一种扩展代码的好方法,无需修改类内部,甚至其公共接口。

多重继承的主要问题是,有人试图将一个人建模为从“手臂”和“腿”继承,然后抱怨多重继承混淆了问题

Mixin来自LISP世界,我从来没有相信他们适合C++所有这一切。然而,多重继承本身并不是坏事,而且是必要的。一件事与不止一件事有“是-是”关系是很常见的

根据经验,我尝试只使用空基类,即Java调用的接口。如果我在基类中需要任何类型的数据,我几乎总是发现我的设计就像试图定义一个person类继承自“arm”和“leg”一样糟糕。也有例外,如果你认为你已经找到了,试着写一个文档,并请几个朋友对其进行评论。经验法则不是正式的数学证明,但它对我非常有用。

经验法则是“尽可能使用最弱的关系”。看一看:和

您可以使用私有继承来建模“以术语实现”,但您应该考虑异常安全含义

我知道,我喜欢本周的大师

编辑

我使用这种基类来 为具有属性的对象提供 预定义的非虚拟功能

为什么聚合比继承更好:

1) 您可以使用继承隐藏方法(或更改名称)。默认情况下,所有方法都可用

2) 您可以使用不带虚拟析构函数的类(std::string和所有stl容器)

3) 您可以轻松地更改实现,如果您想要更改底层对象,那么您的接口将不会受到影响,但是如果您从不同的基类继承,那么您的接口可能会更改

4) 异常安全(参见第三个链接)

5) 容易阅读

当需要“私有继承”时:

1) 当要使用的类是抽象类时(不能使用聚合)

2) 当您想要使用受保护的方法时

当“公共继承”是
template<class T>
class Named: public T {
  // all for named object
};

template<class T>
struct NamedCompare {
  bool operator<const Named<T>* obj1, const Named<T>* obj2) {
    //...
  }
};

template<class T>
class NamedSet {
  typedef std::set<NamedSet<Named<T>*, NamedCompare> type;
};

bool DoSmthWithFoo(Param1 p1,..., NamedSet<Foo>::type& set);
typedef Named<Featured<Blabla<Foo> >  > NamedFeaturedBlablaFoo;
class NamedFeaturedBlablaFoo: public Named<Featured<Blabla<Foo> >  > {};