【问题标题】:Composition: using traits to avoid forwarding functions?组合:使用特征来避免转发功能?
【发布时间】:2014-01-22 02:01:16
【问题描述】:

假设我们有两个类,AB。当使用组合来建模 "has-a""is-implemented-in-terms-of" 关系时(例如 B has-a A),与继承相比的缺点之一是 B 不包含它需要的 A 的公共功能。为了访问As 的公共函数,必须提供转发函数(与继承相反,B 将继承As 的所有公共函数)。

举一个更具体的例子,假设我们有一个Person 有一个 ContactInfo

using namespace std;

class ContactInfo
{
public:
   ContactInfo();
   void updateAddress(string address);
   void updatePhone(string phone);
   void updateEmail(string email);
private:
   string address;
   string phone;
   string email;
};

class Person
{
public:
   Person();
   // Forwarding functions:
   void updateAddress(string address){contactInfo.updateAddress(address)};
   void updatePhone(string phone){contactInfo.updatePhone(phone)};
   void updateEmail(string email){contactInfo.updateEmail(email)};
private:
   ContactInfo contactInfo;
};

忽略设计中的任何缺陷(这只是一个人为的例子来证明我的问题),我不得不在Person 中繁琐地复制来自ContactInfo 的确切函数签名。在一个更复杂的例子中,可能会有许多这样的函数,以及许多层的组合类,这会导致大量代码重复,以及所有常见的维护问题和容易出错等。

尽管如此,这是根据条款 38 等来源对 “has-a”“is-implemented-in-terms-of” 建模的推荐做法Meyers 的 Effective C++ 和 Sutter 的 Exceptional C++ (link) 的第 24 条。

在研究这个问题时,我遇到了this Wikipedia article,它讨论了相同的主题。 At the bottom of the article,建议如下:

使用组合代替继承的一个缺点是所有 由组合类提供的方法必须是 在派生类中实现,即使它们只是转发 方法。 [...] 这个缺点可以通过使用特征来避免。

我对特质的概念还很陌生,而且根据我所阅读的所有内容,我发现很难与上述陈述相关联。因此,我的问题是:如何使用特征来避免通过组合转发函数?基于我的示例(PersonContactInfo)的答案将是理想的。

编辑:为了澄清,为了回应一些答案,我知道私有继承是建模的替代组合"is-implemented-in-terms-of "。我的问题不是关于那个,而是关于维基百科关于特征的声明的含义。我不是在要求作曲的替代品。我加粗了我的问题,以便更清楚地表明这就是我要问的。

【问题讨论】:

  • 我认为维基百科在这方面误导了你。 Traits 是泛型编程的工具,比如说你想包含一些模板类型参数类型的contactInfo
  • “有”关系是“有”关系。没有“人为的例子”,因为在任何情况下,都可以解决决定某事是某事还是有某事的问题。如果您无法解决此问题,那么您在其他地方犯了错误。一个棘手的例子是这样的:汽车是交通工具,飞机是交通工具,那么火车也是交通工具?答案是不。火车是一组车辆(前置发动机、货车,也可以移动,也可以选择后置车辆)。你总是必须做出明确的区分,否则你会遇到麻烦。
  • 我觉得这篇维基百科文章不好:-/
  • @lukasz1985 谢谢,但我的问题不是关于如何区分“has a”和“is a”,而是关于特征。
  • 我认为您不应该使用特质来更轻松地完成工作。您可能弄乱了组合,因为在您的示例中,您应该能够通过使用包含类(如 Person->getContactInfo() )来获取底层对象。好处是不要先使用特征和多重继承。如果这样下去,唯一剩下的就是在继承和组合(IS A 或 HAS A)之间做出决定。您可以在这里查看我的答案:stackoverflow.com/questions/49002/…

标签: c++ oop aggregation composition traits


【解决方案1】:

也许你可以尝试私有继承:

class Person : private ContactInfo
{
public:
   Person() { }
   using ContactInfo::updateAddress;
   using ContactInfo::updatePhone;
   using ContactInfo::updateEmail;
};

int main()
{
    Person person;
    person.updateAddress("hi");
    return 0;
}

尽管您可能需要注意此FAQ 中列出的注意事项:

还有几个区别:

  • 如果您希望每辆车包含多个引擎,则需要简单组合变体
  • 私有继承变体可能引入不必要的多重继承
  • 私有继承变体允许 Car 的成员将 Car* 转换为 Engine*
  • 私有继承变体允许访问基类的受保护成员
  • 私有继承变体允许 Car 覆盖 Engine 的虚函数
  • 私有继承变体使得给 Car 一个 start() 方法稍微简单一些(20 个字符与 28 个字符相比) 简单地调用引擎的 start() 方法

否则所提供的组合示例似乎与您的相同。点击wikipedia文章中的traits链接并没有提供任何C++文章,参考中的链接似乎是关于type traits。我找不到 type traits 与您的场景有什么关系。

【讨论】:

    【解决方案2】:

    文章用Interface谈继承,

    所以事实上它告诉对象必须尊重一些签名。

    类型特征可用于检查签名是否正确并分派给适当的函数

    例如一些STL算法等待类型Iterator, 但这些迭代器不是从class Iterator 继承的,而是必须提供一些契约(operator ++()operator !=(rhs)operator*())。

    以文章为例:

    • 类玩家 - 可以移动
    • 类建筑 - 不能移动

    和代码:

    #if 1
    // simple type traits which tells if class has method update_position
    template <typename T> struct can_move;
    
    // Hardcode the values
    template <> struct can_move<Player> { static const bool value = true; };
    template <> struct can_move<Building> { static const bool value = false; };
    
    #else
    // or even better, but need a has_update_position. (see how to check if member exist in a class)
    template <typename T> struct can_move{ static const bool value = has_update_position<T>::value };
    #endif
    
    template <typename T, bool> struct position_updater;
    
    // specialization for object which can move
    template <typename T> struct position_updater<T, true>
    {
        static void update(T& object) { object.update_position(); }
    };
    
    // specialization for object which can NOT move
    template <typename T> struct position_updater<T, false>
    {
        static void update(T& object) { /* Do nothing, it can not move */ }
    };
    
    
    template <typename T>
    void update_position(T& object)
    {
        // statically dispatch to the correct method
        // No need of interface or inheritance
        position_updater<T, can_move<T>::value>::update(object);
    }
    

    【讨论】:

      【解决方案3】:

      首先我应该提到,特征在 C++/STL 和 PHP、Lasso 等语言中是不同的东西。看起来维基百科的文章指的是类似 PHP 的特征,因为 C++/STL 特征不是为多态重用而设计的(我们正在谈论具有多态行为的代码重用,对吗?)。它们旨在简化模板类的声明。

      特征用于一些不支持多重继承的语言(PHP、Lasso 等)。 Traits 允许“模拟”多重继承(但多重继承和 Traits 并不完全相同)。

      相比之下,C++ 不支持 PHP 特征,但支持多重继承。因此,如果谈到 C++,那么 trait-like 解决方案将是这样的:

      class VisibleTrait
      {
          public:
              virtual void draw();
      };
      
      class SolidTrait
      {
          public:
              virtual void collide(Object objects[]);
      };
      
      class MovableTrait
      {
          public:
              virtual void update();
      };
      
      
      // A player is visible, movable, and solid
      class Player : public VisibleTrait, MovableTrait, SolidTrait
      {
      };
      
      // Smoke is visible and movable but not solid 
      class Smoke : public VisibleTrait, MovableTrait
      {
      };
      
      // A hause is visible and solid but not movable
      class House : public VisibleTrait, SolidTrait
      {
      };
      

      所以回答你的问题

      如何使用特征来避免转发函数 作品?

      1) 特征不会避免使用组合转发函数,因为特征独立于组合工作。 (来自维基百科的文章在特征和组成之间的关系方面有点误导)
      2) 类似 PHP/Lasso 的特征可以在具有多重继承的 C++ 中部分模拟。

      【讨论】:

      • 感谢您提供直接解决我问题的答案。
      • @JBentley,可以使用 c++ 中的特征,以便通过组合提供更好的方法转发。在此处查看我的相关 SO 问题:stackoverflow.com/questions/28393195/…
      【解决方案4】:

      AFAIK 特征类类似于以下内容:

      #include <iostream>
      using namespace std;
      
      class ContactInfo
      {
      public:
         void updateAddress() { cout << "update address"; };
         void updatePhone() {};
         void updateEmail() {};
      };
      
      template<class T> class TraitClass
      {
        public:
      
        private:
          T obj;
      };
      
      template<> class TraitClass<ContactInfo>
      {
        public:
              void updateAddress() {obj.updateAddress();};
              void updatePhone() {obj.updatePhone();};
              void updateEmail() {obj.updateEmail();};
        private:
          ContactInfo obj;
      };
      
      class Person
      {
      public:
              void updateAddress() {obj.updateAddress();};
              void updatePhone() {obj.updatePhone();};
              void updateEmail() {obj.updateEmail();};
      private:
          TraitClass<ContactInfo> obj;
      };
      
      int main() {
      
          Person myPerson;
      
          myPerson.updateAddress();
      
          return 0;
      }
      

      即:一个编译时模板类,您可以在其中实现(和/或专门化)您的委托方法,以便转发您需要的任何内容,而无需(无论出于何种原因)重复继承。

      http://en.wikipedia.org/wiki/Trait_(computer_programming)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-17
        • 2015-04-08
        • 1970-01-01
        • 2011-08-01
        相关资源
        最近更新 更多