C++ - 在派生类中静态初始化受基类保护的成员变量

C++ - Initialise base class protected member variables statically in derived class

本文关键字:基类 保护 变量 成员 初始化 静态 派生 C++      更新时间:2023-10-16

我们正在尝试在我们的项目中设置有界primitive types,我们可以检查派生类的实例是否具有有效范围内的数据值(minmax成员变量,在基class中受保护)对于该派生class

我的问题是,有没有办法静态初始化派生类的minmax变量,每个派生class只需一次,而不是每次实例化派生class

C#,这将在静态初始化块中,但我不确定如何在C++中做到这一点。

我知道我可以在派生class constructor中初始化它们,但这似乎每次都浪费。

我想我正在寻找抽象数据成员,在基类中声明,但在派生类中静态定义。

class BoundedFloat
{
public:
BoundedFloat(const float v) : Value(v) {}
// some common methods that use Min and Max
// prefer to implement in base class rather than in each derived class
bool withinBounds();
bool breachedLowerThreshold();
bool breachedUupperThreshold();

protected:
const float Min;
const float Max;
float Value;
}

bool BoundedFloat::withinBounds()
{
return ((Value >= Min) && (Value<= Max));
}
bool BoundedFloat::breachedLowerThreshold()
{
return (Value < Min);
}
bool BoundedFloat::breachedUupperThreshold()
{
return (Value > Max);
}
class Temperature : public BoundedFloat
{
public:
Temperature(const float v) : BoundedFloat(v) {}
// seems wasteful to do this each time, when min and max only need 
// initialised once per derived class
// Temperature(const float v) : BoundedFloat(v, -40.0f, 80.0f)
// statically initialise Temperature's Min and Max in base class here somehow?
private:
// I know this is wrong, but it indicates the functionality I'm looking for.
override static float Min;
override static float Max;
}

我认为除了重新设计之外,没有任何方法可以精确地做你想要的。请记住,对于未标记为static的成员,类类型的每个对象都有自己的这些成员副本。如果你有多个类型为Temperature的对象,每个对象都有自己的MinMax,所以所有这些都需要在某个时候初始化。此外,基类BoundedFloat无法知道它正在使用几个可能的派生类中的哪一个,除非您以某种方式让它知道,并且只是传递最小值和最大值可能是"让它知道"的最简单方法。

但是与此相关的"浪费"(编码和维护工作?CPU 运行时?成员变量使用的内存?对我来说似乎并没有那么大。

不过,您可能需要考虑一些重新设计选项:

  • 将数据从成员更改为虚拟 getter 函数。

    class BoundedFloat
    {
    public:
    BoundedFloat(const float v) : Value(v) {}
    virtual ~BoundedFloat() = default;
    protected:
    BoundedFloat(const BoundedFloat&) = default;
    BoundedFloat(BoundedFloat&&) = default;
    BoundedFloat& operator=(const BoundedFloat&) = default;
    BoundedFloat& operator=(BoundedFloat&&) = default;
    float Min() const = 0;
    float Max() const = 0;
    float Value;
    };
    class Temperature : public BoundedFloat
    {
    public:
    Temperature(const float v) : BoundedFloat(v) {}
    float Min() const override { return -40.0f; }
    float Max() const override { return 80.0f; }
    };
    

    现在,每个派生类都只负责其最小/最大值。尽管这种方法有其自身的成本,但对您来说可能很重要,也可能不重要。

  • 蝇量级模式

    class BoundedFloat
    {
    protected:
    struct Data {
    float Min;
    float Max;
    };
    BoundedFloat(const float v, const Data& data_in)
    : Value(v), data(data_in) {}
    protected:
    BoundedFloat(const BoundedFloat&) = default;
    BoundedFloat(BoundedFloat&&) = default;
    BoundedFloat& operator=(const BoundedFloat&) = default;
    BoundedFloat& operator=(BoundedFloat&&) = default;
    float Value;
    const Data& data;
    };
    class Temperature : public BoundedFloat
    {
    public:
    Temperature(const float v) : BoundedFloat(v, my_data) {}
    private:
    static const Data my_data;
    };
    const Temperature::Data Temperature::my_data{ -40.0f, 80.0f };
    

    这里只有派生类数据值的一次初始化,它们几乎自然地"在"基类中;只是你需要将它们称为data.Mindata.Max。 当每个类有大量数据不变时,通常使用此模式,但您当然也可以只对几条数据使用它。 这里的编码成本与传入单个基值相当,或者可能更少,并且程序的运行时成本可能会上升或下降,具体取决于。

不,你不能。

此外,如果您希望不同的派生类具有不同的最小值/最大值,则基类中的静态成员无论如何都不起作用。(如果你不这样做,你可以在基中静态初始化)。 每个派生类将共享相同的最小值和最大值实例。 因此,一旦您更改/初始化了派生类中的(非静态)值,基类和其他派生类也将更改它们。请参阅此链接。

我认为您需要最小值、最大值虚拟函数。喜欢这个:

class BoundedFloat{
public:
BoundedFloat(const float v) : Value(v) {}
virtual float Min() { return -10; }
virtual float Max() { return 10; }
protected:
float Value;
}
class Temperature : public BoundedFloat
{
public:
Temperature(const float v) : BoundedFloat(v) {}
virtual float Min() override { return -40; }
virtual float Max() override { return 80; }
}

您可以使用基类的模板并使用派生类型作为基类模板的类型来获取所需的内容。 这用于启用静态多态性,称为奇怪的重复模板模式 (CRTP),但在本例中,我仅将其用作标记,因此每个派生类都可以在基类中拥有自己的一组静态数据成员。 将基类更改为

template<typename Derived>
class BoundedFloat
{
public:
BoundedFloat(const float v) : Value(v) {}
static float getMax() { return Max; }
static float getMin() { return Min; }
protected:
static const float Min;
static const float Max;
float Value;
};

意味着BoundedFloat<Derived>有自己的MinMax. 所以我们从BoundedFloat派生出来

class Temperature : public BoundedFloat<Temperature>
{
public:
Temperature(const float v) : BoundedFloat(v) {}
};

然后我们必须定义我们想要的值,比如

template<>
const float BoundedFloat<Temperature>::Min = -40.0f;
template<>
const float BoundedFloat<Temperature>::Max = 80.0f;

您可以看到此实时示例中的代码


请注意,如果您想在派生类中使用基类静态成员,则需要在行外防御中执行此操作。 这必须像这样完成,因为如果函数体在派生类中,那么它需要隐式实例化静态成员,我们不能发生这种情况,因为显式实例化是在该类之后。

根据@NathanOliver的建议,这是我目前得到的。

它满足了我想要的东西:

  • 在基类中声明为 const 的通用值
  • 随后在派生类中定义的公共值,仅定义一次,这使构造函数保持干净
  • 基类中可以使用派生的 const 值的常用方法

这种方法有什么严重的问题吗?

BaseTypes.h

namespace Common
{
template<typename Base>
class BoundedFloat
{
public:
BoundedFloat(const float v) : Value(v) {}
// Common methods that read Min, Max, Value
void displaySummary() const;
bool withinBounds() const;
// bool breachedLowerThreshold() const;
// bool breachedUpperThreshold() const
protected:
static const float Min;
static const float Max;
float Value;
};
template <typename Base>
void BoundedFloat<Base>::displaySummary() const
{
std::cout <<
"Min = " << Min << ", "
"Max = " << Max << ", "
"Value = " << Value << std::endl;
if (withinBounds())
{
cout << "within bounds" << endl;
}
else
{
cout << "outwith bounds" << endl;
}
}
template <typename Base>
bool BoundedFloat<Base>::withinBounds() const
{
return ((Value >= Min) && (Value <= Max));
}
}

传感器阅读.h

#pragma once
#include "BaseTypes.h"
namespace Common
{
class SensorReading: public BoundedFloat<SensorReading>
{
public:
SensorReading(const float v) : BoundedFloat(v) {}
};
template<>
const float BoundedFloat<SensorReading>::Min = -2.0f;
template<>
const float BoundedFloat<SensorReading>::Max = 7.5f;
}

温度.h

#pragma once
#include "BaseTypes.h"
namespace Common
{
class Temperature : public BoundedFloat<Temperature>
{
public:
Temperature(const float v) : BoundedFloat(v) {}
};
template<>
const float BoundedFloat<Temperature>::Min = -40.0f;
template<>
const float BoundedFloat<Temperature>::Max = 80.0f;
}

主.cpp

int main()
{
Temperature temperature{ 80.0f };
temperature.displaySummary();
SensorReading sensorReading{ 72.5f };
sensorReading.displaySummary();
return 0;
}