Может ли неиспользуемая функция создать шаблон переменной с побочными эффектами в соответствии с C ++ 14? - PullRequest
0 голосов
/ 08 декабря 2018

Вот мой код:

#include <iostream>

class MyBaseClass
{
public:
    static int StaticInt;
};

int MyBaseClass::StaticInt = 0;

template <int N> class MyClassT : public MyBaseClass
{
public:
    MyClassT()
    {
        StaticInt = N;
    };
};

template <int N> static MyClassT<N> AnchorObjT = {};

class UserClass
{
    friend void fn()
    {
        std::cout << "in fn()" << std::endl; //this never runs
        (void)AnchorObjT<123>;
    };  
};

int main()
{
    std::cout << MyBaseClass::StaticInt << std::endl;
    return 0;
}

Вывод:

123

... указывает на то, что конструктор MyClassT() был вызван, несмотря на то, что fn() никогда не вызывался.

Проверено на gcc и clang с -O0, -O3, -Os и даже -Ofast


Вопрос

Имеет ли эта программаиметь неопределенное поведение в соответствии со стандартом C ++?

Другими словами: если более поздние версии компиляторов обнаружат, что fn() никогда не будет вызвано, могут ли они оптимизировать создание экземпляра шаблона вместе с запуском конструктора?

Можно ли каким-то образом сделать этот код детерминированным, то есть принудительно запустить конструктор - без ссылки на имя функции fn или значение параметра шаблона 123 вне UserClass?


ОБНОВЛЕНИЕ: Модератор усек мой вопрос и предложил дальнейшее усечение.Оригинальную подробную версию можно посмотреть здесь .

Ответы [ 3 ]

0 голосов
/ 08 декабря 2018

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

template <typename T> void bar();

void foo(bool b) {
  if (b) {
    bar<int>();
  } else {
    bar<double>();
  }
}

Здесь создаются экземпляры bar<int> и bar<double>, даже если foo никогда не вызывается или даже если foo вызывается только с true.

Для шаблона переменной, в частности, применяется правило [temp.inst] / 6 :

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

В вашей функции:

friend void fn() 
{ 
    (void)AnchorObjT<123>;
};  

AnchorObjT<123> упоминается в контексте, который требует определения (независимо от того, вызывается ли fn() когда-либо или даже, в этом случае, даже возможно вызвать)следовательно, он создан.

Но AnchorObjT<123> - глобальная переменная, поэтому ее создание означает, что у нас есть объект, созданный до main() - к моменту ввода main() конструктор AnchorObjT<123> будет запущен, установив StaticInt в 123.Обратите внимание, что нам не нужно на самом деле запускать fn() для вызова этого конструктора - здесь роль fn() заключается просто в создании экземпляра шаблона переменной, его конструктор вызывается в другом месте.

Печать 123 правильна,ожидаемое поведение

Обратите внимание, что хотя для существования языка необходим глобальный объект AnchorObjT<123>, компоновщик может по-прежнему оставаться объектом, поскольку на него нет ссылок.Предполагая, что ваша реальная программа делает больше с этим объектом, если вам нужно, чтобы он существовал, вам, возможно, придется сделать с ним больше, чтобы компоновщик не удалил его (например, gcc имеет атрибут used ).

0 голосов
/ 08 декабря 2018

Короткий ответ: он работает.

Длинный ответ: он работает , если компоновщик не отбросит всю вашу единицу перевода (.obj).

Это может произойтикогда вы создаете .lib и связываете его.Компоновщик обычно выбирает, какой .obj связать из библиотеки, на основе графа зависимостей «я использую символы, которые экспортирует obj».

Так что, если вы используете эту технику в файле cpp, файлы cpp не имеютСимволы, которые используются в другом месте вашего exexutable (в том числе косвенно через другие obj в вашей lib, которые в свою очередь используются исполняемым файлом), компоновщик может отбросить файл obj yoir.

Я сталкивался с этим с clang.Мы где создавали саморегистрационные фабрики, а некоторые где сбрасывали.Чтобы исправить это, мы создали несколько макросов, которые привели к существованию тривиальной зависимости, предотвращающей удаление файла obj.

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

0 голосов
/ 08 декабря 2018

«Если последующим версиям компиляторов удастся обнаружить, что fn () никогда не будет вызываться [и] они оптимизируют удаление шаблона», то эти компиляторы будут сломаны.

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

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

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

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

PS - все вышеперечисленное предполагаетнет неопределенного поведения в коде C ++.С неопределенным поведением, входящим в изображение, все правила выходят в окно.

...