Подтвердить что ты не робот

Определите постоянные переменные в заголовке C++

Программа, над которой я работаю, имеет множество констант, которые применяются во всех классах. Я хочу создать один файл заголовка "Constants.h" и уметь объявлять все соответствующие константы. Тогда в других классах я могу включить #include "Constants.h.

Я отлично работал, используя синтаксис #ifndef... #define .... Однако я предпочел бы использовать форму констант const int.... Я не совсем уверен, как это сделать.

4b9b3361

Ответ 1

Вы можете просто определить серию const ints в файле заголовка:

// Constants.h
#if !defined(MYLIB_CONSTANTS_H)
#define MYLIB_CONSTANTS_H 1

const int a = 100;
const int b = 0x7f;

#endif

Это работает, потому что в С++ имя в области пространства имен (включая глобальное пространство имен), явно объявленное как const, а не явно объявленное extern, имеет внутреннюю связь, поэтому эти переменные не будут вызывать дубликаты символов при связывании единиц перевода. В качестве альтернативы вы можете явно объявлять константы как статические.

static const int a = 100;
static const int b = 0x7f;

Это более совместимо с C и более читаемо для людей, которые могут быть не знакомы с правилами привязки С++.

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

enum mylib_constants {
    a = 100;
    b = 0x7f;
};

Все эти методы используют только заголовок и позволяют объявленным именам использоваться в качестве констант времени компиляции. Используя extern const int, и отдельный файл реализации предотвращает использование имен в качестве констант времени компиляции.


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

int * const ptr;

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

Также обратите внимание, что это одна из причин, по которой я предпочитаю последовательно помещать const после типа: int const вместо const int. Я также помещаю * рядом с переменной: т.е. int *ptr; вместо int* ptr;.

Мне нравится делать такие вещи, потому что они отражают общий пример того, как работает С++. Альтернативы (const int, int* p) являются просто специальными, чтобы сделать некоторые простые вещи более удобочитаемыми. Проблема в том, что, когда вы выходите из этих простых случаев, специальные альтернативы с обсадной оболочкой становятся активно вводящими в заблуждение.

Итак, хотя более ранние примеры показывают общее использование const, я бы рекомендовал людям писать их следующим образом:

int const a = 100;
int const b = 0x7f;

и

static int const a = 100;
static int const b = 0x7f;

Ответ 2

Мне нравится пространство имен для этой цели.

Вариант 1:

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H

//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace LibConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}
#endif

// source.cpp
#include <LibConstants.hpp>
int value = LibConstants::CurlTimeOut;

Вариант 2:

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H
//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace CurlConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}

namespace MySQLConstants
{
  const int DBPoolSize = 0xFF;      // Just some example
  ...
}
#endif



// source.cpp
#include <LibConstants.hpp>
int value = CurlConstants::CurlTimeOut;
int val2  = MySQLConstants::DBPoolSize;

И я бы никогда не использовал класс для хранения таких переменных типа HardCoded Const.

Ответ 3

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

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

Что-то вроде этого заголовочного файла:

// Protect against multiple inclusions in the same source file
#ifndef CONSTANTS_H
#define CONSTANTS_H

extern const int CONSTANT_1;

#endif

И это в исходном файле:

const int CONSTANT_1 = 123;

Ответ 4

C++ 17 inline переменные

Эта удивительная функция C++ 17 позволяет нам:

  • удобно использовать только один адрес памяти для каждой константы
  • сохранить его как constexpr: Как объявить constexpr extern?
  • сделать это в одной строке из одного заголовка

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Скомпилируйте и запустите:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream.

Смотрите также: Как работают встроенные переменные?

C++ стандарт для встроенных переменных

Стандарт C++ гарантирует, что адреса будут одинаковыми. C++ 17 n4659 стандартный черновик 10.1.6 "Встроенный спецификатор":

6 Встроенная функция или переменная с внешней связью должны иметь одинаковый адрес во всех единицах перевода.

cppreference https://en.cppreference.com/w/cpp/language/inline объясняет, что если static не указан, то он имеет внешнюю связь.

Реализация встроенных переменных

Мы можем наблюдать, как это реализовано с помощью:

nm main.o notmain.o

который содержит:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

и man nm говорит о u:

"U" Символ является уникальным глобальным символом. Это расширение GNU для стандартного набора привязок символов ELF. Для такого символа динамический компоновщик будет следить за тем, чтобы во всем процессе                есть только один символ с этим именем и используемым типом.

поэтому мы видим, что для этого есть выделенное расширение ELF.

C++ 17 стандартного проекта "global" const подразумевает static

Это цитата из того, что было упомянуто по адресу: fooobar.com/questions/180823/...

C++ 17 n4659 стандартный черновик 6.5 "Программа и связь":

3 Имя, имеющее область имен (6.3.6), имеет внутреннюю связь, если это имя

  • (3.1) - переменная, функция или шаблон функции, которые явно объявлены статическими; или,
  • (3.2) - не встроенная переменная энергонезависимого const-квалифицированного типа, которая явно не объявлена как extern или ранее объявленный иметь внешнюю связь; или
  • (3.3) - член данных анонимного объединения.

"Пространство имен" - это то, что мы часто называем "глобальным".

Приложение C (информативное) Совместимость, C.1.2  Раздел 6: "основные понятия" дает обоснование, почему это было изменено с C:

6.5 [also 10.1.7]

Изменение: имя области действия файла, которая явно объявлена как const, а не явно объявлена как extern, имеет внутренняя связь, в то время как в C она будет иметь внешнюю связь.

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

Влияние на исходную функцию: изменение семантики четко определенной функции.

Difficulty of converting: Semantic transformation.

How widely used: Seldom.

См. также: Почему const подразумевает внутреннюю связь в C++, а в C - нет?

Протестировано в GCC 7.4.0, Ubuntu 18.04.

Ответ 5

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

Constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

class GlobalConstants {
  public:
    static const int myConstant;
    static const int myOtherConstant;
};

#endif

Constants.cpp

#include "Constants.h"

const int GlobalConstants::myConstant = 1;
const int GlobalConstants::myOtherConstant = 3;

Тогда вы можете использовать его так:

#include "Constants.h"

void foo() {
  int foo = GlobalConstants::myConstant;
}

Ответ 6

Кажется, что ответ bames53 может быть расширен до определения целочисленных и нецелых константных значений в пространствах имен и объявлениях классов, даже если они включены в несколько исходных файлов. Нет необходимости помещать объявления в заголовочный файл, а определения в исходный файл. Следующий пример работает для Microsoft Visual Studio 2015, для z/OS V2.2 XL C/C++ в OS/390 и для g++ (GCC) 8.1.1 20180502 в GNU/Linux 4.16.14 (Fedora 28)). Обратите внимание, что константы объявлены/определены только в одном заголовочном файле, который включается в несколько исходных файлов.

В foo.cc:

#include <cstdio>               // for puts

#include "messages.hh"
#include "bar.hh"
#include "zoo.hh"

int main(int argc, const char* argv[])
{
  puts("Hello!");
  bar();
  zoo();
  puts(Message::third);
  return 0;
}

В messages.hh:

#ifndef MESSAGES_HH
#define MESSAGES_HH

namespace Message {
  char const * const first = "Yes, this is the first message!";
  char const * const second = "This is the second message.";
  char const * const third = "Message #3.";
};

#endif

В bar.cc:

#include "messages.hh"
#include <cstdio>

void bar(void)
{
  puts("Wow!");
  printf("bar: %s\n", Message::first);
}

В zoo.cc:

#include <cstdio>
#include "messages.hh"

void zoo(void)
{
  printf("zoo: %s\n", Message::second);
}

В bar.hh:

#ifndef BAR_HH
#define BAR_HH

#include "messages.hh"

void bar(void);

#endif

В зоопарке:

#ifndef ZOO_HH
#define ZOO_HH

#include "messages.hh"

void zoo(void);

#endif

Это дает следующий вывод:

Hello!
Wow!
bar: Yes, this is the first message!
zoo: This is the second message.
Message #3.

Тип данных char const * const означает постоянный указатель на массив постоянных символов. Первый const необходим, потому что (согласно g++) "ISO C++ запрещает преобразование строковой константы в 'char *'". Второе значение const необходимо, чтобы избежать ошибок в связи с множественными определениями (тогда недостаточно постоянными) констант. Ваш компилятор может не жаловаться, если вы пропустите одно или оба из const, но тогда исходный код будет менее переносимым.