Что лучше хранить константы класса в членах данных или в методах?

StackOverflow https://stackoverflow.com/questions/647648

  •  22-07-2019
  •  | 
  •  

Вопрос

Недавно я написал класс, который отображает кривые B-сплайна.Эти кривые определяются рядом контрольных точек.Изначально я намеревался использовать восемь контрольных точек, поэтому добавил в класс константу, например:

class Curve
{
   public:
      static const int CONTROL_POINT_COUNT = 8;
};

Теперь я хочу расширить этот класс, чтобы разрешить произвольное количество контрольных точек.Поэтому я хочу изменить это на:

class Curve
{
   public:
      int getControlPointCount() {return _controlPointCount;}
};

Вопрос в том, не лучше ли для начала хранить константы в методах, чтобы облегчить адаптивность.Другими словами, не лучше ли начать так:

class Curve
{
   public:
      int getControlPointCount() {return 8;}
};

Преимущество этого в том, что я мог бы просто изменить один символ в рассматриваемом методе вместо перемещения констант и т. д.

Это хорошая практика или плохая?

Это было полезно?

Решение

Обычно я предпочитаю поддерживать как можно меньше соединений вручную.

Количество контрольных точек на кривой — это, ну, количество контрольных точек на кривой.Это не независимая переменная, которую можно установить по своему желанию.

Поэтому я обычно предоставляю константную ссылку на стандартный контейнер:

class Curve
{   
    private:
        std::vector<Point>& _controlPoints;

    public:      
        Curve ( const std::vector<Point>& controlPoints) : _controlPoints(controlPoints)
        {
        }

        const std::vector<Point>& getControlPoints ()
        {
            return _controlPoints;
        }
};

А если вы хотите узнать, сколько контрольных точек, то используйте curve.getControlPoints().size().Я подозреваю, что в большинстве случаев использования вам в любом случае потребуются точки, а также счетчик, и, открывая стандартный контейнер, вы можете использовать идиомы идиомы итератора стандартной библиотеки и встроенные алгоритмы, вместо того, чтобы получать счетчик и вызывать функция типа getControlPointWithIndex в петле.

Если в классе кривой действительно больше ничего нет, я мог бы даже пойти так далеко:

typedef std::vector<Point> Curve;

(часто кривая не отображается сама по себе, поскольку класс средства рендеринга может содержать подробную информацию о конвейере рендеринга, оставляя кривую чисто геометрическим артефактом)

Другие советы

int getControlPointCount() {return _controlPointCount;}

Это аксессор. Замена констатной статики для средства доступа на самом деле не является преимуществом, как указывал Литб. Что вам действительно нужно для будущего , так это, вероятно, пара аксессоров и мутаторов.

int getControlPointCount() {return _controlPointCount;} // accessor

Я бы также добавил design-const для средства доступа и сделал бы это:

int getControlPointCount() const {return _controlPointCount;} // accessor

и соответствующий:

void setControlPointCount(int cpc) { _controlPointCount = cpc;} //mutator

Теперь большая разница со статическим объектом заключается в том, что счетчик контрольных точек больше не является атрибутом уровня класса, а является уровнем экземпляра. Это изменение дизайна . Вы хотите это таким образом?

Nit: Ваш статический счетчик уровня класса равен public и, следовательно, не требует доступа.

Чтобы лучше ответить на ваш вопрос, нужно также знать, как установлена ​​переменная controlPointCount.Он установлен за пределами вашего класса?В этом случае вам также следует определить сеттер.Или класс Curve несет единоличную ответственность за его установку?Он установлен только во время компиляции или также во время выполнения.

В любом случае, избегайте магического числа даже в такой форме:

int getControlPointCount() {return 8;}

Это лучше:

int getControlPointCount() {return CONTROL_POINT_COUNT;}

Преимущество метода заключается в том, что вы можете изменить внутреннюю реализацию (использовать постоянное значение, читать из файла конфигурации, динамически изменять значение), не затрагивая внешнюю часть класса.

class Curve
{   
    private:
        int _controlPointCount;

        void setControlPointCount(int cpc_arg)
        {
            _controlPointCount = cpc_arg;
        }

    public:      
        curve()
        {
            _controlPointCount = 8;
        }

        int getControlPointCount() const
        {
            return _controlPointCount;
        }
};

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

Понимая вопрос, у меня есть ряд концептуальных проблем с примером:

  • Какова возвращаемая стоимость getControlPointCount() когда количество контрольных точек не ограничено?
    • Это МАКСИНТ?
    • Это текущее количество контрольных точек на кривой (нарушая тем самым логику, говорящую, что это максимально возможное количество точек?)
  • Что произойдет, если вы попытаетесь создать кривую с точками MAXINT?В конце концов у вас закончится память.

Сам интерфейс мне кажется проблематичным.Как и другие стандартные классы коллекций, этот класс должен был инкапсулировать ограничение на количество точек и AddControlPoint() должен был вернуть ошибку, если произошло ограничение размера, памяти или любое другое нарушение.

Что касается конкретного ответа, я согласен с Кгианнакакисом:функция-член обеспечивает большую гибкость.

Я склонен использовать конфигурацию + константу (значение по умолчанию) для всех «стабильных» значений при выполнении программы. С простыми константами для значений, которые не могут изменяться (360 градусов -> 2 пи радианы, 60 секунд -> 1 минута) или чье изменение может нарушить работающий код (минимальные / максимальные значения для алгоритмов, которые делают их нестабильными).

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

Если все кривые должны иметь одинаковое количество контрольных точек в вашем приложении, то это значение уровня класса (статическое). Если разные кривые могут иметь разное количество контрольных точек, то это не значение уровня класса, а уровень экземпляра.

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

// Assuming that different curves can have different 
// number of control points, but that the value cannot 
// change dynamically for a curve.
class Curve
{
public:
   explicit Curve( int control_points )
      : control_points_( control_points )
   {}
   // ...
private:
   const int control_points_;
};

namespace constant
{
   const int spline_control_points = 8;
}
class Config
{
public:
   Config();
   void readFile( std::string const & file );

   // returns the configured value for SplineControlPoints or
   // constant::spline_control_points if the option does not 
   // appear in config.
   int getSplineControlPoints() const;
};

int main()
{
   Config config;
   config.readFile( "~/.configuration" ); // read config

   Curve c( config.getSplineControlPoints() );
}

Для целочисленного типа я обычно использую:

class Curve
{
   public:
      enum 
      {
          CONTROL_POINT_COUNT = 8
      };
};

Если константе не нужны никакие сущности, кроме реализации класса, я объявляю константы в файле * .cpp.

namespace
{
const int CONTROL_POINT_COUNT = 8;
}

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

В этом конкретном случае я бы сделал что-то вроде следующего:

class Curve
{

   protected:

      int getControlPointCount() {return _controlPointCount;}
      int setControlPointCount(int c) { _controlPointCount = c; }

   private:

      static int _controlPointCount = 0;
};

Константы вообще не должны быть определены внутри методов. У выбранного вами примера есть две уникальные особенности. Во-первых, это добытчик; во-вторых, возвращаемый тип - это int. Но смысл определения констант состоит в том, чтобы использовать их более одного раза и иметь возможность ссылаться на них удобным способом. Ввод "8" в отличие от «controlPointCount» может сэкономить ваше время и, возможно, не потребует затрат на обслуживание, но обычно это не так, если вы всегда определяете константы внутри методов.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top