C ++ программа времени компиляции уникальных чисел - PullRequest
4 голосов
/ 03 августа 2009

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

template<class T, class U, int uniqueify>
class foo
{
...
}

Но теперь вам нужно вручную убедиться, что каждый раз, когда вы используете foo, вы передаете ему другое значение для uniqueify. Наивное решение состоит в том, чтобы использовать __LINE__ так:

#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)

Это решение имеет проблему, хотя - __LINE__ получает сброс для каждой единицы перевода. Таким образом, если две единицы перевода используют шаблон в одной строке, шаблон создается только один раз. Это может показаться маловероятным, но представьте себе, насколько сложно отладить ошибку компилятора, если бы она произошла. Точно так же вы можете попробовать как-то использовать __DATE__ в качестве параметра, но с точностью до секунды, и это время начала компиляции, а не когда она достигает этой строки, поэтому, если вы используете параллельную версию make, довольно вероятно иметь две единицы перевода с одинаковыми __DATE__.

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

Еще одно решение, это использовать __FILE__ и __LINE__ вместе:

#define MY_MACRO_IMPL(file, line) foo<T, U, file, line>
#define MY_MACRO MY_MACRO_IMPL(T, U, __FILE__, __LINE__)

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

Даже если это сработало, содержит ли __FILE__ абсолютный путь к файлу или просто имя самого файла не определено в стандарте, поэтому, если у вас есть два одинаковых именованных файла в разных папках, это может все еще сломаться Итак, вот мое решение:

#ifndef toast_unique_id_hpp_INCLUDED
#define toast_unique_id_hpp_INCLUDED

namespace {
namespace toast {
namespace detail {

template<int i>
struct translation_unit_unique {
    static int globally_unique_var;
};

template<int i>
int translation_unit_unique<i>::globally_unique_var;

}
}
}

#define TOAST_UNIQUE_ID_IMPL(line) &toast::detail::translation_unit_unique<line>::globally_unique_var
#define TOAST_UNIQUE_ID TOAST_UNIQUE_ID_IMPL(__LINE__)

#endif

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

Пример использования:

template<int* unique_id>
struct special_var
{
    static int value;
}

template<int* unique_id>
int special_var<unique_id>::value = someSideEffect();

#define MY_MACRO_IMPL(unique_id) special_var<unique_id>
#define MY_MACRO MY_MACRO_IMPL(TOAST_UNIQUE_ID)

И foo.cpp становится:

#include <toast/unique_id.hpp>

...

typedef MY_MACRO unique_var;
typedef MY_MACRO unique_var2;
unique_var::value = 3;
unique_var2::value = 4;
std::cout << unique_var::value << unique_var2::value;

Несмотря на то, что шаблон является одним и тем же, а пользователь не предоставляет никаких дифференцирующих параметров, unique_var и unique_var2 различаются.

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

Я не знаю, справился ли я лучше всего с объяснением , почему это было бы полезно, но ради этого обсуждения, клянусь;)

Ответы [ 2 ]

1 голос
/ 15 октября 2014

Эта техника в целом небезопасна по двум причинам.

  1. __LINE__ может быть равно в двух разных строках в одной и той же единице перевода, либо с помощью директив #line, либо (чаще) с использованием одного и того же номера строки в нескольких заголовочных файлах.

  2. У вас будут нарушения ODR, если вы используете TOAST_UNIQUE_ID или что-либо, полученное из него, во встроенной функции или определении шаблона в файле заголовка.

Тем не менее, если вы никогда не используете это в заголовочных файлах и не используете #line в своем основном исходном файле и используете макрос только один раз в строке, это кажется безопасным. (Вы можете снять это последнее ограничение, переключившись с __LINE__ на __COUNTER__.)

1 голос
/ 03 августа 2009

Это должно быть безопасно, но более простым способом было бы просто использовать FILE . Кроме того, int недостаточно на 64-битной платформе. Используйте intptr_t:

template<const char *file, int line>
class unique_value {
  static char dummy;
  unique_value() { }
public:
  static intptr_t value() { return (intptr_t)&dummy; }
};

#define UNIQUE_VALUE (unique_value<__FILE__, __LINE__>::value())

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

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

...