C ++ - Статически инициализировать переменные-члены, защищенные базовым классом, в производном классе - PullRequest
1 голос
/ 03 апреля 2019

Мы экспериментируем с ограничением primitive types в нашем проекте, где мы можем проверить, что экземпляры производных классов имеют значение данных в допустимом диапазоне (min и max переменные-члены, защищенные в базе class)для этого производного class.

Мой вопрос: есть ли способ, которым я могу статически инициализировать переменные min и max производного класса, только один раз на производное 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;
}

Ответы [ 4 ]

2 голосов
/ 03 апреля 2019

Нет, вы не можете.

Более того, статические члены в базовом классе все равно не будут работать, если вы хотите разные значения Min / Max для разных производных классов.(Если вы не можете инициализировать в базе статически).Каждый производный класс будет иметь одинаковые экземпляры Min и Max.Таким образом, после того как вы изменили / инициализировали (не статически) значения в производном классе, они также будут изменены для базового класса и других производных классов.См. эту ссылку .

Я думаю, вам нужны виртуальные функции 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; }
}
2 голосов
/ 03 апреля 2019

Я не думаю, что есть какой-то способ сделать именно то, что вы хотите, за исключением небольшого изменения дизайна.Помните, что для члена, не помеченного static, каждый объект типа класса имеет свою собственную копию этих членов.Если у вас есть несколько объектов типа Temperature, каждый из них имеет свои собственные Min и Max, поэтому все они должны быть инициализированы в какой-то момент.Кроме того, базовый класс BoundedFloat не может знать, в каком из нескольких возможных производных классов он используется, если вы как-то не дадите ему об этом знать, и просто передача значений min и max является, вероятно, самым простым способом «дать ему знать»..

Но «расточительность», связанная с этим (кодирование и обслуживание? Время работы процессора? Память, используемая переменными-членами?), Не кажется мне такой уж большой.

Тем не менее,Есть несколько вариантов редизайна, которые вы можете рассмотреть:

  • Измените данные из членов на виртуальные функции получения.

    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; }
    };
    

    Теперь каждый производный класс несет полную ответственностьдля его минимальных / максимальных значений.Хотя этот подход имеет свои собственные затраты, которые могут или не могут быть важны для вас.

  • Шаблон Flyweight

    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.Min и data.Max.Этот шаблон обычно используется, когда имеется много данных, которые являются постоянными для каждого класса, но вы также можете использовать его только с несколькими частями данных.Стоимость кодирования здесь сравнима с передачей отдельных базовых значений или, может быть, меньше, а затраты времени выполнения программы могут увеличиваться или уменьшаться немного, в зависимости.

1 голос
/ 03 апреля 2019

Вы можете получить желаемое, используя шаблон для базового класса и используя производный тип в качестве типа для шаблона базового класса. Он используется для включения статического полиморфизма и известен как Любопытно повторяющийся шаблон (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> имеет свои Min и Max. Таким образом, мы получаем из 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;

Вы можете увидеть код, работающий в этом живом примере


Обратите внимание, что если вы хотите использовать статический член базового класса в своем производном классе, вам нужно делать это вне определения строки. Это должно быть сделано так, потому что, если бы тело функции находилось в производном классе, тогда ему нужно было бы неявно создать экземпляр статического члена, и этого не может быть, поскольку явное создание экземпляра следует за этим классом.

0 голосов
/ 03 апреля 2019

Основываясь на предложении @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;
}

Температура.ч

#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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...