Pull to refresh

И ещё раз про уникальные константы

Reading time 3 min
Views 12K
Прочитав статью «Вычислите длину окружности», которая, в общем-то, крайне позабавила меня своим стилем, и узнав для себя кое-что новое, я стал несколько сомневаться в достаточной подробности предложенной информации. Всё-таки компиляторов довольно много, систем тоже немало, а в статье как-то навеяно Windows и Visual Studio (на правах ИМХО).

Речь пойдёт о примерах после вынесения «загадочной» константы за пределы функции. Так как правда об этом действе хуже рассказанной хитрым профессором. Примечание: я тестил всё исключительно «на имеющихся под рукой»:
  • OS X 10.10 c gcc 4.9.2 и clang 3.5 / 3.6
  • Ubuntu 14.10 с clang 3.5
  • Windows с MinGW-w64-32 и gcc 4.9.2

Не исключаю чего-то ещё более неординарного под другими системами. Буду рад, если кто-то расскажет о них мне.

А правда ли constexpr может всё?


Учёный студент, закончив институт и придя на работу разработчиком C++ (скажем, под основные десктопные системы, Linix, OS X, Windows), узнаёт, что в компании все уже перешли на C++11 совместимые компиляторы. Обрадованный возможностью писать проще и короче, герой в одном из заголовочных файлов пишет так:

constexpr char sin_tables[4096] { /* Заполните значениями, если очень хочется */};

Примерно через час после коммита из соседнего отдела, который тестит сборки под OS X, раздаётся недоумевающий крик, что бинарнику сильно поплохело. Всё просто, в отличие от Visual C++ (определённых новых версий), ни clang (3.5, 3.6), ни gcc (4.8, 4.9) не полагаются на подобное неустановленное поведение (компилятор Microsoft отчего-то перестал видеть в constexpr обычную переменную с внутренней линковкой и сделал, впрочем, доброе дело), и мы получили дублирование нашего массива.

Пример:
Скрытый текст
Пример, написанный на коленке:

a.cpp

#include <cstdio>

#include "h.h"

void printSin1() {
	for (auto &e : sin_tables)
		printf("%f\n", e);
}

int main() {
	printSin1();
	printSin2();
}

b.cpp

#include <cstdio>

#include "h.h"

void printSin2() {
	for (auto &e : sin_tables)
		printf("%f\n", e);
}


h.h

constexpr float sin_tables[4096] { /* ... */};

void printSin1();
void printSin2();


Для тех, кому лень собирать:

Скрытый текст




Если вы считаете, что виновата специфичная работа компиляторов на OS X, то спешу вас уверить, оно так же и в Ubuntu, и в среде MinGW. Это глобальная особенность компиляторов clang и gcc…

А если попробовать старый «проверенный» способ?


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

#if !defined(CONST_UNIQUE)
	#if defined(MSVC)
		#define CONST_UNIQUE constexpr
	#else
		#define CONST_UNIQUE extern __attribute__((weak)) constexpr
	#endif
#endif

Всё работает, всё собирается. Но через пару минут из той же комнаты доносятся новые вопли: нерабочий коммит, билдбот выкинул ошибку, что же ты делаешь?! В проблеме опять же нет ничего необычного. Clang, хоть и делающий попытки реализовать всё нестандартное в gcc, полностью совместим никогда не был и вряд ли будет. Ну и потому в особых случаях использования этого атрибута нас будет ждать сюрприз (ну как же без этого).

CONST_UNIQUE int A = 137; // gcc OK, clang OK
CONST_UNIQUE int B = A+1; // gcc OK, clang OK

#include <array>
std::array<int, A> loveClang; // gcc OK, clang FAIL


./file.cpp:21:17: error: non-type template argument is not a constant expression
std::array<int, A> loveClang;
               ^

Так-то всё правильно, как этот человек вообще посмел написать хак, да и отправить его в транк. История умалчивает, что в особо «понимающих» случаях в MinGW может быть применён хак selectany, вместо weak, как более Windows-подобный. Однако от вышесказанной ошибки он тоже не спасёт.

И что же делать? И как же быть?


Как показывает мой личный опыт, магические константы обычно специфичны для какого-то блока или модуля программы. Засорять ими глобальное пространство имён требуется достаточно редко, а потому сейчас никто вам не запретит написать:

MyClass.h

class MyClass {
	/* ... */
	static constexpr float m_pi {3.14};
	/* ... */
};

MyClass.cpp

constexpr float MyClass::m_pi;

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

Впрочем, вам не кажется, что вышесказанное — это серьёзное зацикливание на очень небольшой проблеме? В реальности все и так понимают:
  • что константы типа пи есть в хедерах (причём часто даже в микроконтроллерах);
  • что если нужна магическая константа, она чаще бывает целой, чем дробной, а значит скорее всего оптимизируется компилятором (притянуто за уши, да);
  • под что-то константное и большое не так жалко написать пару лишних строк (раньше я, например, использовал static const в классах или extern, если надо, в других местах);
  • что константы с одинаковыми значениями, но разными именами (алиасами) компилятор и без weak может оптимизировать в одну в некоторых случаях.
Tags:
Hubs:
+23
Comments 8
Comments Comments 8

Articles