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

Posted

技术标签:

【中文标题】C++ - 在派生类中静态初始化基类受保护的成员变量【英文标题】:C++ - Initialise base class protected member variables statically in derived class 【发布时间】:2019-04-03 12:30:50 【问题描述】:

我们正在尝试在我们的项目中使用有界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;

【问题讨论】:

这在 C++ 中是不可能的。如果要初始化非静态成员,每次构造都必须完成。 它并没有真正增加太多(如果有的话)开销,如果成员是非静态的,则无论如何都需要为每个实例初始化它们。也许你想让他们static @user463035818,好点。是的,我确实希望它们在派生类中是静态的,并在每个派生类中定义一次。但我希望它们最初在基类中声明,我会在其中放置使用它们的常用方法。 【参考方案1】:

我认为没有任何方法可以精确地完成您想要的操作,除非进行一些重新设计。请记住,对于未标记为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。当每个类都有大量数据时,通常会使用这种模式,但您当然也可以只使用几条数据。这里的编码成本与传入单个基值相当,或者可能更少,并且程序的运行时成本可能会有所上升或下降,具体取决于。

【讨论】:

感谢您的建议,@aschepler。我所说的“浪费”是指不必为每个类实例化提供 Min 和 Max 值,并且可能会误导经验不足的开发人员。对我来说,最小/最大初始化似乎更正确,静态地,每个派生类,让每个实例的构造函数只初始化值本身。【参考方案2】:

不,你不能。

此外,如果您想为不同的派生类设置不同的最小值/最大值,则基类中的静态成员无论如何都不起作用。 (如果你不这样做,你可以在基础中静态初始化)。 每个派生类将共享相同的 Min 和 Max 实例。 因此,一旦您在派生类中更改/初始化(非静态)值,它们也将针对基类和其他派生类进行更改。见this link。

我认为您需要 Min、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; 

【讨论】:

感谢您的回复。我不一定要像这样暴露 Min 和 Max。但我确实希望它们在基类中声明,以及使用它们的方法,但在每个派生类中定义,并具有适合该特定派生类的值。【参考方案3】:

您可以使用基类模板并使用派生类型作为基类模板的类型来获得所需的内容。这用于启用静态多态性,被称为Curiously recurring template pattern (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&lt;Derived&gt; 有自己的MinMax。所以我们派生自BoundedFloatlike

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;

你可以看到代码在这个live example中工作


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

【讨论】:

或者,您可以跳过基类中的成员声明,​​要求每个派生类具有这些成员名称,而基类中的代码可以做到static_cast&lt;const Derived*&gt;(this)-&gt;Min 这看起来真的很接近我想要的,@NathanOliver。但是,我不希望在每个派生类中都有像 getMax 这样的方法。我希望使用 Min、Max 的常用方法位于基类中 - 我已经更新了原始帖子以说明我的意思。 @ArtieLeech 我已经用它的工作方式/外观更新了答案。 @NathanOliver,上面的第一行应该说“template”,而不是 Derived?因为这将是其余部分派生的“基础”有界浮点类? @ArtieLeech 我称之为Derived,因为那里使用的类型应该派生自BoundedFloat【参考方案4】:

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

它满足我的要求:

通用值声明为基类中的常量 公共值随后定义在派生类中,只有一次,这使构造函数更干净 基类中可以使用派生 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));
        
    

SensorReading.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;

main.cpp

int main()

    Temperature temperature 80.0f ;
    temperature.displaySummary();

    SensorReading sensorReading 72.5f ;
    sensorReading.displaySummary();

    return 0;

【讨论】:

以上是关于C++ - 在派生类中静态初始化基类受保护的成员变量的主要内容,如果未能解决你的问题,请参考以下文章

派生类 C++ 中基类受保护成员的访问声明为公共的

可以通过[重复]在派生类中初始化受保护的基类成员

C++ | 类继承

详解C++中基类与派生类的转换以及虚基类

C ++:如何在派生类中定义基类构造函数,如果基构造函数具有带有私有成员的初始化列表[重复]

获取指向基类受保护成员函数的函数指针