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

Создаются ли аргументы объекта для фиктивных параметров?

Предположим, что шаблон функции:

template <class T>
void foo(T /* dummy */) {...}

Предположим, что foo вызывается так:

foo(Widget());

Будет ли объект Widget построен в этом случае?

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

Однако рассмотрим следующий текст в разделе 2.5 Современный С++ от Alexandrescu:

Теперь скажем, что в вашем приложении есть правило: Объекты типа Widget - неприкасаемый старый код и должны принимать два аргумента при построении, а второй - фиксированное значение, такое как -1. У ваших собственных классов, полученных из Widget, нет этой проблемы.

...

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

template <class T, class U>
T* Create(const U& arg, T /* dummy */)
{
   return new T(arg);
}
template <class U>
Widget* Create(const U& arg, Widget /* dummy */)
{
   return new Widget(arg, -1);
}

Такое решение повлекло бы за собой накладные расходы на создание произвольно сложного объекта, который остается неиспользованным.

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

Итак, что правильно? Если Alexandrescu прав, то почему эта оптимизация не происходит?

4b9b3361

Ответ 1

Создание объекта может иметь побочные эффекты.

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

В качестве примера предположим, что виджеты зарегистрировали свое существование с центральным расположением. Когда объект был создан, количество существующих виджета увеличилось бы на 1 - что не может быть незаконным в соответствии со стандартом.

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

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

Ответ 2

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

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


Возьмем std::string как пример, просто для параметра sake (lol):

единица перевода 1

// (N.B. no variable name; therefore "dummy")
void foo(std::string)
{}

единица перевода 2

#include <string>

void foo(std::string argument);

int main()
{
   foo("will this be used to construct a std::string?");
}

Да, поскольку в отношении TU2 это должно быть.


В обратном примере листинг конструкции, очевидно, будет ужасной ошибкой:

единица перевода 1

#include <string>
#include <iostream>

void foo(std::string argument)
{
    std::cout << argument << std::endl;
}

единица перевода 2

#include <string>

// (N.B. no variable name; therefore "dummy")
void foo(std::string);

int main()
{
   foo("will this be used to construct a std::string?");
}

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


По характеру модели компиляции С++ оба этих сценария на 100% не поддаются определению.