【问题标题】:Mechanisms to pass properties to base class将属性传递给基类的机制
【发布时间】:2020-12-08 22:02:48
【问题描述】:

将属性从派生类传递到基类的机制是什么,以便:

  • 基础 ctor 可以访问它们。
  • 它们可以轻松存储在 STL 容器中。
  • 外部的任何人都可以访问它们,只能在派生类上“看到”。
  • 有效性并不是很有价值。

属性是与派生类的每个对象相关的常量。

【问题讨论】:

  • 在您的代码中没有“与派生类的每个对象相关的常量”,因此代码和文本之间的联系不清楚。您发布的代码有什么问题/缺少什么?
  • @idclev463035818 虚函数代表常量。
  • 所以问题是如何在基类构造函数中访问它们?或者还有什么可以代替虚函数?
  • @idclev463035818 是的
  • 我无法遵循您针对要点 3 的论点。如果您需要一个值来构造一个对象,那么将其作为参数传递给构造函数是自然的,而不是“受阻”。如果您不喜欢公开构造函数,则可以将其保护。此外,这并不“假定允许创建具有任何属性的基础对象”。 (至少不会像任何构造函数那样或多或少),基类和派生类仍然可以检查有效参数并采取相应措施。

标签: c++ oop derived-class


【解决方案1】:

如果您的问题是“将属性从派生类传递到基类的机制是什么?”有两种方法可以做到这一点。

  1. 很常见的方式是通过成员初始化列表来初始化父类
#include <iostream>

class Base
{
public:
    Base(const char *a)
    {
        std::cout << a << std::endl;
    }
};

class Derived : public Base
{
public:
    Derived(const char *a)
    : Base{a} { }
};

int main()
{
    Derived d{"Parent initialization through member initializer list"};
    // console output: Parent initialization through member initializer list
}
  1. 使用 using 键获取构造函数继承
#include <iostream>

class Base
{
public:
    Base(const char *a)
    {
        std::cout << a << std::endl;
    }
};

class Derived : public Base
{
public:
    using Base::Base;
};

int main()
{
    Derived d{"Parent class initialization through constructor inheritance"};
    // console output: Parent class initialization through constructor inheritance
}

用模板类看一个有用的例子

#include <iostream>
#include <vector>

template<class T>
class custom_vector : public std::vector<T>
{
public:
    // I will inherit all std::vector constructors
    using std::vector<T>::vector;
    // ...
};

int main()
{
    // Inizializer list constructor
    custom_vector<int> v{1, 2, 3, 4, 5};
    for (auto x : v) std::cout << x << std::endl;
    // 1 2 3 4 5
 
    // Fixed-allocation with default value constructor
    custom_vector<char> v2(10, 'A');
    for (auto x : v2) std::cout << x << std::endl;
    // A A A A A A A A A A
}

如果没有使用 std::vector::vector,我就不会继承 std::vector 构造函数。

在您的示例中,我注意到一些设计缺陷

  • 虚拟函数不用于将属性传递给基类
  • 我不明白你为什么提到模板
  • 您不应该重复 virtual 关键字,因为它在派生类中已经是虚拟的,使用 override 关键字就可以了。
  • 一般不建议部分实现抽象类,您应该考虑从 BaseVehicle 中删除 m_image 和 m_speed。在某些情况下,抽象类中的某些实现实际上很有用,但只有在有充分理由的情况下才这样做。
  • 在抽象类的构造函数中有这样的语句 *table.registerVehicle(this, number) 是一种糟糕的设计症状。

【讨论】:

    【解决方案2】:

    您可以通过虚拟方式或非虚拟方式进行操作。但要回答其中哪一个是最好的,让我们看看你的例子:

    class Bicycle: public BaseVehicle
    {
    public:
        Bicycle(VehicleTable& table, Number number): BaseVehicle(table, number) {}
    
        virtual float getMaxSpeed() const { return 40.0f; } // constant
        virtual float getWeight()   const { return 10.0f; } // constant
        virtual float getLength()   const { return 2.3f;  } // constant
        virtual int   getWheels()   const { return 2;     } // constant
    };
    

    这些函数中的每一个都返回一个常量,但仍需要虚拟调用才能获得该常量。这是不好的做法,因为您需要执行(昂贵的)虚拟调用来获取每个值。

    更好的方法:使用struct

    相反,最好将这些常量传递给父类的构造函数并将常量存储在基类中。这样,您根本不需要访问BaseVehiclevtable,只需在基类中直接访问它们即可。像这样:

    struct VehicleStats {
        float maxSpeed;
        float weight;
        float length;
        int wheels;
    };
    
    // BaseVehicle's constructor now becomes this:
    BaseVehicle(VehicleTable& table, Number number, VehicleStats stats)
    

    或者,您可以在BaseVehicle 中只创建一个virtual 函数:

    virtual VehicleStats getStats() const = 0;
    

    这肯定比创建四个单独的函数要好。

    替代方法:使用enum

    您还可以将代表车辆类型的enum 传递给父级。例如:

    enum class VehicleType {
        BICYCLE,
        CAR
    };
    
    // BaseVehicle's constructor now becomes this:
    BaseVehicle(VehicleTable& table, Number number, VehicleType stats)
    

    然后您可以全局存储统计信息:

    static const std::map<VehicleType, VehicleStats> globalStatsMap;
    

    如果您有大量统计数据并且不想增加基类的内存大小,这是一个非常好的方法。您现在也根本不需要BaseVehicle 的实例,仍然可以根据类型查找属性。

    【讨论】:

      【解决方案3】:

      从显示的内容来看,您根本不需要继承:

      struct VehicleAttributes
      {
          float maxSpeed;
          float weight;
          float length;
          int   wheelsCount;
      };
      
      class Vehicle
      {
      private:
          Vehicle(VehicleTable& table, Number number, const VehicleAttributes& attributes): m_image(attributes.length, attributes.wheelsCount), m_attributes(&attributes)
          {
              table.registerVehicle(*this, number);
          }
      
      public:
          static Vehicle BiCycle(VehicleTable& table, Number number)
          {
              static const VehicleAttributes bicycleAttributes{40, 10, 2.3, 2};
      
              return Vehicle(table, number, bicycleAttributes);
          }
          static Vehicle Car(VehicleTable& table, Number number)
          {
              static const VehicleAttributes carAttributes{88, 42, 2.5, 4};
      
              return Vehicle(table, number, carAttributes);
          }
      
          const VehicleAttributes& getAttributes() const { return *m_attributes; }
      
      private:
          Image m_image;
          const VehicleAttributes* m_attributes = nullptr;
          float m_speed = 0; // the current speed
      };
      

      【讨论】:

      • 这段代码很糟糕。你仍然在初始化列表中调用getLength()getWheels(),你拼错了VehicleAttributes,你拼错了vehicleAttributes,你没有用地址初始化m_attributes,你默认分配nullptr给它即使它总是被初始化。
      • @J.Schultke:我没有意识到之前的构造函数调用了虚方法。我希望我能修正错别字(使用 IDE/编译器更容易捕捉到;-))。我忘了&amp; 获取地址(也有错字:/)。我更喜欢默认为nullptr 初始化每个指针/内置,即使总是初始化(在出错的情况下更容易调试)。
      猜你喜欢
      • 1970-01-01
      • 2013-11-13
      • 1970-01-01
      • 2011-10-16
      • 2021-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-03
      相关资源
      最近更新 更多