【问题标题】:Creating a class member that is automatically calculated from other class members?创建一个从其他类成员自动计算的类成员?
【发布时间】:2017-07-18 16:35:49
【问题描述】:
  1. 在编程方面我绝对是新手,我正在尝试通过解决 C++ 中的一些简单“问题”来自学基础知识。

  2. 我已经在网上搜索了我的问题的确切答案,然后将其发布到这里,但到目前为止还没有找到,但这可能是因为 (1)。

所以,我正在寻找一种声明类成员的方法,该类成员会自动从同一类的其他成员中计算出来,以便可以像显式定义的类成员一样使用计算出的类成员。例如,想象一个名为 bio 的结构,它具有属性/成员 bio.numberofhands、bio.fingersperhand 以及最后的属性 bio.totalfingers,它会自动从上述成员中计算出来。

这是最接近我想要实现的目标的示例:

#include <iostream>

typedef struct creature {
    int numberofhands;
    int fingersperhand;
    int totalfingers();
} creature;    

int creature::totalfingers()
{
    return numberofhands * fingersperhand;
};

int main()
{
    creature human;
    human.numberofhands = 2;
    human.fingersperhand = 5;

    printf("%d",human.totalfingers());
    return(0);
}

真正让我烦恼的是,我必须将计算出的值与明确定义的值区别对待,即我必须在其后面加上“()”。 如何更改代码,以便我可以使用:human.totalfingers 而无需明确定义?

【问题讨论】:

  • C++ 中不需要typedef struct
  • 您可以将您的普通会员更改为也使用(),或者使用operator int() 创建一个代理对象,但如果没有帮助,最后一个将无法使用printf 的省略号。跨度>
  • 我实际上不会这样做,但是对于有趣的事情,您可以为要调用的每个函数定义一个标记,其语法与变量 #define totalNoOfFingers creature::totalfingers() 相同。

标签: c++ class


【解决方案1】:

最简单的选择是使用公共成员函数并隐藏实际属性。 像这样的:

class Creature {
public:
    Creature(int numhands, int fingersperhand) // constructor
        : m_numhands{numhands}, m_fingersperhand{fingersperhand}
        { }
    int fingersPerHand() const { return m_fingersperhand; }
    int numberOfHands() const { return m_numhands; }
    int totalFingers() const { return numberOfHands() * fingersPerHand(); }

private:
    const int m_numhands;
    const int m_fingersperhand;
};

私有成员变量是一个实现细节。该类的用户只需使用三个公共成员函数在构造后获取不同数量的手指,而无需关心其中两个返回常量存储数字而第三个返回计算值 - 这与用户无关。

使用示例:

#include <iostream>
int main()
{
    Creature human{2, 5};
    std::cout << "A human has "
        << human.totalFingers() << " fingers. "
        << human.fingersPerHand() << " on each of their "
        << human.numberOfHands() << " hands.\n";
    return 0;
}

如果 - 根据您的评论 - 您不想使用构造函数(尽管这是确保您不会忘记初始化成员的最安全方法),您可以修改类像这样:

class CreatureV2 {
public:
    int fingersPerHand() const { return m_fingersperhand; }
    int numberOfHands() const { return m_numhands; }
    int totalFingers() const { return numberOfHands() * fingersPerHand(); }

    void setFingersPerHand(int num) { m_fingersperhand = num; }
    void setNumberOfHands(int num) { m_numhands = num; }

private:
    // Note: these are no longer `const` and I've given them default
    // values matching a human, so if you do nothing you'll get
    // human hands.
    int m_numhands = 2;
    int m_fingersperhand = 5;
};

修改类的使用示例:

#include <iostream>
int main()
{
    CreatureV2 human;
    std::cout << "A human has "
        << human.totalFingers() << " fingers. "
        << human.fingersPerHand() << " on each of their "
        << human.numberOfHands() << " hands.\n";

    CreatureV2 monster;
    monster.setFingersPerHand(7);
    monster.setNumberOfHands(5);
    std::cout << "A monster has "
        << monster.totalFingers() << " fingers. "
        << monster.fingersPerHand() << " on each of their "
        << monster.numberOfHands() << " hands.\n";

    CreatureV2 freak;
    freak.setFingersPerHand(9);
    // Note: I forgot to specify the number of hands, so a freak get 
    // the default 2.
    std::cout << "A freak has "
        << freak.totalFingers() << " fingers. "
        << freak.fingersPerHand() << " on each of their "
        << freak.numberOfHands() << " hands.\n";

    return 0;
}

注意:以上所有内容均假设您使用的是现代 C++14 编译器。

【讨论】:

  • 如何用这种方式定义某个生物?
  • 好的,我看到你添加了一个使用示例。但是,现在我看到的问题是,当我定义“人类”时,我必须记住我声明“生物”成员的顺序,而不是通过他们的“名称”来定义它。如果我有 50 个成员的名字很容易记住(如手、脚、四肢、鼻子、耳朵等),但没有可以轻松推断出的特定顺序怎么办?
  • @TrveBlack 我希望答案现在可以完全回答您的问题 :-)
  • 这种方法的问题是每次访问时都必须重新计算派生属性。理想的解决方案是仅在基础变量发生变化时才对其进行计算。如果所涉及的计算很耗时,这很重要。
【解决方案2】:

您所描述的是封装和“成员变量应该是私有的”是 C++ 中推荐的做事方式的原因之一。

如果每个变量都通过函数访问,那么一切都是一致的,从成员变量重构为计算是可能的。

某些语言,如 C# 或 D,具有“属性”的概念,它提供了解决问题的方法,但 C++ 没有这样的结构。

【讨论】:

    【解决方案3】:

    为了好玩,避免额外括号的代理方式,(但有一些额外费用):

    class RefMul
    {
    public:
        RefMul(int& a, int& b) : a(a), b(b) {}
    
        operator int() const { return a * b; }
    
    private:
        int& a;
        int& b;
    };
    
    struct creature {
        int numberofhands;
        int fingersperhand;
        RefMul totalfingers{numberofhands, fingersperhand};
    };
    

    Demo

    注意:要将RefMulprintf 一起使用,您必须转换为int

    printf("%d", int(human.totalfingers));
    

    如果您使用 c++ 方式打印,则不需要该转换:

    std::cout << human.totalfingers;
    

    【讨论】:

    • 虽然这在技术上回答了这个问题,但在问题中提出的上下文中使用这样的东西是非常非常糟糕的设计。
    • @Frank:我同意不应该使用它。
    【解决方案4】:

    如果您追求一致性,则可以反过来进行更改。用简单地返回成员变量副本的常量方法替换两个成员变量。这样,您访问数据的方式是一致的,您不必担心某些代码会更改不应该更改成员变量的值。

    【讨论】:

      【解决方案5】:

      其他人提供了非常好的答案。如果您正在寻找一致性,可能最简单的方法是使用成员函数(正如@Jesper Juhl 所回答的那样)。

      另一方面,如果您严格希望使用从其他成员自动计算的类成员,则可以使用属性。属性(在 C# 和 Groovy 中定义)不是 C++ 的标准特性,但有一些方法可以在 C++ 中实现它们。 This SO question 很好地概述了在 C++ 中定义和使用属性的方式。我最喜欢的定义属性的方法是利用 Visual C++ 中的 Microsoft 特定的属性扩展(显然,这种方法是特定于 Microsoft Visual C++ 的)。 Visual C++ 中的属性文档可以在MSDN 中找到。使用 Visual C++ 中的属性,您的代码可以修改为:

      struct creature {
          int numberofhands; // use of public member variables are generally discouraged
          int fingersperhand;
      
          __declspec(property(get = get_totalfingers)) // Microsoft-specific
          int totalfingers;
      
      private:
          int fingers;
          int get_totalfingers() 
          {
              return numberofhands * fingersperhand; // This is where the automatic calculation takes place. 
          }
      }; 
      

      这个类可以这样使用:

      #include <iostream>
      int main()
      {
          creature martian;
          martian.numberofhands = 2;
          martian.fingersperhand = 4; // Marvin the Martian had 4!
      
          // This line will print 8
          std::cout << "Total fingers: " << martian.totalfingers << std::endl;
      
          return 0;
      }
      

      正如我之前所说,属性不是 C++ 的标准特性,但有一些方法可以在 C++ 中获取它们,这些方法要么依赖智能技巧,要么使用编译器特定的特性。恕我直言,使用简单的功能(如@Jesper Juhl 所述)是更好的选择。

      【讨论】:

        猜你喜欢
        • 2020-12-04
        • 2018-12-18
        • 2020-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多