Где объявлять/определять константы области класса в С++?

Мне интересно узнать о преимуществах/недостатках различных вариантов объявления и определения констант в C++. Долгое время я просто объявлял их в верхней части заголовочного файла перед определением класса:

//.h
const int MyConst = 10;
const string MyStrConst = "String";
class MyClass {
...
};

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

Недавно я решил, что было бы лучше объявить специфичные для класса константы внутри самого определения класса:

//.h
class MyClass {
    public:
         static const int MyConst = 10;
...
    private:
         static const string MyStrConst;
...
};
//.cpp
const string MyClass::MyStrConst = "String";

Видимость константы будет регулироваться в зависимости от того, используется ли константа только внутри класса или она необходима для других объектов, использующих этот класс. Это то, что я думаю, является лучшим вариантом прямо сейчас, главным образом потому, что вы можете сохранить внутренние константы класса закрытыми для класса, а любые другие классы, использующие общедоступные константы, будут иметь более подробную ссылку на источник константы (например, MyClass: : MyConst). Это также не будет загрязнять глобальное пространство имен. Хотя у этого есть недостаток, требующий неинтегральной инициализации в файле cpp.

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

Просто ищу мнения и, возможно, другие варианты, которые я еще не рассматривал.


person bsruth    schedule 11.01.2010    source источник


Ответы (7)


Ваше утверждение о том, что объявление неинтегральной константы в качестве члена статического класса «имеет ущерб, требуя неинтегральной инициализации в файле cpp», так сказать, не совсем надежно. Это требует определения в файле cpp, но это не «ущерб», это вопрос вашего намерения. Объект const уровня пространства имен в C++ по умолчанию имеет внутреннюю связь, что означает, что в исходном варианте объявление

const string MyStrConst = "String"; 

эквивалентно

static const string MyStrConst = "String"; 

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

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

extern const string MyStrConst; 

и предоставьте определение в файле cpp

const string MyStrConst = "String";

тем самым убедившись, что вся программа использует один и тот же постоянный объект. Другими словами, когда речь идет о нецелочисленных константах, обычной практикой является определение их в файле cpp. Таким образом, независимо от того, как вы это объявляете (в классе или вне), вам обычно всегда приходится иметь дело с «ущербом» от необходимости определять его в файле cpp. Конечно, как я сказал выше, с константами пространства имен вы можете обойтись тем, что у вас есть в вашем первом варианте, но это будет просто пример «ленивого кодирования».

В любом случае, я не думаю, что есть смысл усложнять вопрос: если константа имеет очевидную "привязанность" к классу, ее следует объявлять как член класса.

P.S. Спецификаторы доступа (public, protected, private) не контролируют видимость имени. Они контролируют только его доступность. Имя остается видимым в любом случае.

person AnT    schedule 11.01.2010
comment
Могу я спросить, в чем разница между видимостью и доступностью? Я думаю, что они одинаковы, вы можете привести пример? - person toolchainX; 14.03.2013
comment
@toolchainX: пример: функция-член может быть частной, но функция может быть «видимой» для компилятора в том смысле, что мы предполагаем, что определение существует, и компилятор может «видеть» его. Компилятор просто запрещает доступ [Ошибка компилятора]. Удаление определения или размещение его в каком-либо «скрытом» месте сделает функцию в этих терминах «невидимой» — даже для функций-членов — независимо от доступа [Ошибка ссылки]. - person wardw; 31.05.2013
comment
Было бы вредно помещать ее в файл .cpp, потому что компилятор не может встроить константу, потому что она не находится в единице трансляции компилятора. Если константа представляет собой просто некоторый класс, обертывающий целое число, то было бы лучше иметь его в заголовочном файле, чтобы компилятор знал, каково его значение во всех единицах компиляции. - person qbt937; 14.12.2014

Загрязнение глобального пространства имен — это плохо, потому что кто-то (например, автор используемой вами библиотеки) может захотеть использовать имя MyConst для другой цели. Это может привести к серьезным проблемам (библиотеки, которые нельзя использовать вместе и т. д.).

Ваше второе решение явно лучше, если константы связаны с одним классом. Если это не так просто (подумайте о физических или математических константах без привязки к классу в вашей программе), лучше использовать пространство имен. Кстати: если вы должны быть совместимы со старыми компиляторами C++, помните, что некоторые из них не могут использовать интегральную инициализацию в заголовочном файле - в этом случае вы должны инициализировать в файле C++ или использовать старый трюк enum.

Я думаю, что нет лучших вариантов для констант - по крайней мере, на данный момент не могу придумать...

person hjhill    schedule 11.01.2010

Очевидно, что загрязнять глобальное пространство имен — это плохо. Если я включаю заголовочный файл, я не хочу сталкиваться или отлаживать конфликты имен с константами, объявленными в этом заголовке. Эти типы ошибок действительно разочаровывают, и иногда их трудно диагностировать. Например, однажды мне пришлось сделать ссылку на проект, для которого это было определено в заголовке:

#define read _read

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

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

Я также видел, как люди помещали константы в свой собственный класс, который можно реализовать как синглтон. Мне это кажется работой без вознаграждения, язык предоставляет вам некоторые возможности для объявления констант.

person James Thompson    schedule 11.01.2010

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

person Alex Brown    schedule 11.01.2010
comment
Да, но в этом случае вы должны использовать static или анонимное пространство имен - если вы этого не сделаете, в некоторых реализациях вы загрязните глобальное пространство имен, используемое компоновщиком, и получите конфликты имен при связывании... - person hjhill; 11.01.2010

Лично я использую ваш второй подход; Я использовал его в течение многих лет, и он работает хорошо для меня.

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

person Len Holgate    schedule 11.01.2010

Если только один класс будет использовать эти константы, объявите их как static const внутри тела класса. Если куча связанных классов будет использовать константы, объявите их либо внутри класса/структуры, которая содержит только константы и служебные методы, либо внутри выделенного пространства имен. Например,

namespace MyAppAudioConstants
{
     //declare constants here
}

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

namespace MyAppGlobalConstants
{
    //declare constants here
}
person Chinmay Kanchi    schedule 11.01.2010

не загрязняйте глобальное пространство имен, загрязняйте локальное.

namespace Space
  {
  const int Pint;
  class Class {};
  };

Но практически...

class Class
  {
  static int Bar() {return 357;}
  };
person ima    schedule 11.01.2010
comment
Это не ясно. Что такое константа Pint и почему Bar() возвращает 375? - person Paul Wintz; 08.08.2018