Эквивалентность статической функции для методов - PullRequest
0 голосов
/ 09 июня 2018

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

   struct somedata { ... }

   static somefunc (somedata *self) { ... }

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

Мой вопрос, как мне сделать это с классами и методами?Например, возьмите этот заголовочный файл, который практически идентичен использованию класса и метода вместо функции и явного указателя объекта:

   struct someclass {
      void method ();
   }

   void someclass::method () { ... }

Очевидно, я не могу использовать static someclass::method, потому чтоэто совсем другое.

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

Я могу объявить все эти методы inline, которые будут работать, но будут иметь нежелательный эффект, в частности, попросить компилятор встроить их дажекогда это не имеет смысла.

Я упускаю что-то очевидное или C ++ не имеет эквивалента static для методов?На мой взгляд (надеюсь, что это не так), единственные варианты для меня - это либо переместить все эти методы в отдельную единицу перевода, либо пометить их как встроенные - кажется, что нет эквивалента внутренней связи в стиле C.

Обновление: Я думаю, что этот вопрос был преждевременно закрыт как дубликат.Предположительно повторяющийся вопрос заключается в том, будут ли функции маркировки как встроенные всегда встроены в них.Этот вопрос о том, как избежать правила ODR, как это делает статическое ключевое слово для простых функций, или объяснить, что это не может быть сделано в C ++, на который не отвечает другой вопрос, который просто говорит спрашивающему не беспокоиться об этом.В моем вопросе inline упоминается только как возможное (но плохое) решение.

Обновление 2: Несколько раз упоминалось, что inline не является запросом на встраивание функции или чтоСтандарт C использует только inline для обхода правила ODR и не просит компилятора встроить функцию.

Оба утверждения явно не соответствуют действительности.Например, просмотр документации GCC или исходного кода LLVM показывает, что широко используемые компиляторы рассматривают inline как запрос для вставки функции.Я также цитирую C ++ 03, который говорит (в 7.1.2.2):

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

Таким образом, существующие компиляторы и стандарт C ++ (я только проверил 148882: 2003) явно не согласны с неоднократным утверждением «inline влияет только на ODR».

Такое неправильное восприятие кажетсябыть довольно широко распространенным, как видно, например, здесь: https://blog.tartanllama.xyz/inline-hints/, где кто-то исследует это утверждение, глядя на фактический исходный код GCC / LLVM и обнаруживает, что оба компилятора обрабатывают inline как фактический запрос на встраивание.

ОднакоИмейте в виду, что мой вопрос о том, как получить эффект static для функций-членов в C ++, или в качестве альтернативы, чтобы получить более четкое утверждение, что C ++ просто не имеет этой функции для функций-членов, только для простых функций.Свойства inline здесь имеют отношение только в той степени, в которой это решает проблему за счет потенциально нежелательного встраивания, что может ухудшить производительность.

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

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

Обновление 3 : Чтобы пояснить пример - методы не содержат статических переменных, и это не имеет значенияприводит ли конечный результат к нескольким физическим различным копиям метода или нет, если все такие копии ведут себя одинаково.Фактический пример такого метода:

char *reserve (int bytes)
{
  if (left <= bytes)
    flush ();

  if (left <= bytes)
    throw std::runtime_error ("bulkbuf allocation overflow");

  return cur;
}

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

Обновление 4 : есть многократные утверждения, что компиляторы универсально игнорируют ключевое слово inline как запрос на встраивание, несмотря на убедительные доказательства того, что это неверно,.

Просто чтобы избавиться от каких-либо сомнений, я попробовал это с помощью этой (довольно бессмысленной) программы:

//inline
int f(int i)
{   
  return i < 0 ? 0 : f(i-1) + 1;
} 

int main(int argc, char *[])
{   
  return f(5) + f(argc);
} 

Обратите внимание на закомментированное ключевое слово inline.

Когда я компилирую это с g ++ 6.3.0 (выпущено в 2016 году) из Debian GNU / Linux Stretch (с использованием g++ -Os -S -o - test.C), я получаю эту программу main, когда inline закомментирован:

    movl    %edi, %ecx
    movl    $5, %edi
    call    f(int)
    movl    %eax, %edx
    movl    %ecx, %edi
    call    f(int)
    addl    %edx, %eax
    ret

И это, когда inline активен в:

        xorl    %eax, %eax
.L3:
        cmpl    %eax, %edi
        js      .L2
        incl    %eax
        jmp     .L3
.L2:
        addl    $6, %eax
        ret

Так что без inline функция не была встроена, с inline она была встроена.По крайней мере, это доказывает, что компиляторы не всегда игнорируют inline как запрос на встраивание, как часто утверждается (и g ++, безусловно, является одним из немногих основных компиляторов C ++, и версия 6.3 вряд ли устарела, так что это нестранный нишевый компилятор).

Фактически, как стандартный, так и существующий компиляторы обрабатывают inline как нечто большее, чем просто изменение поведения ODR, а именно как явный запрос на включение функции.

Примечанието, что компилятор игнорирует подсказку или нет, имеет отношение только к моему вопросу, касательно языка C ++, а не каких-либо компиляторов, и, по крайней мере, C ++ 03 требует от компиляторов «преимущественно» встроенных функций, помеченных как таковые, не требуя ихчтобы сделать это, поэтому мои опасения по поводу inline действительны независимо от того, игнорируют ли это компиляторы.

Обновление 5:

Изменение f на это:

вернуть я <0?1: f (i-1) + f (i-2); </p>

приводит к аналогичному поведению как с clang ++ 3.8.1-24, так и с g ++.Кроме того, Всегда ли c ++ 11-совместимые компиляторы всегда игнорируют встроенные подсказки? утверждает, что MSVC также возглавляет ключевое слово inline как запрос фактической вставки.

g ++, clang ++ / LLVM и MSVC вместе покрываютбольшая доля «рынка» C ++, поэтому можно с уверенностью сказать, что компиляторы почти всегда обрабатывают inline как запрос на встраивание, независимо от того, прислушиваются ли они к этому или нет, и в дополнение к другим требованиям стандарта C ++.

Ответы [ 2 ]

0 голосов
/ 09 июня 2018

Семантика static и inline для функций, не являющихся членами, отличается, даже если определения функций в противном случае идентичны.

// in several translation units
static void foo_static() { 
   static int bar; // one copy per translation unit
}

// in several translation units
inline void foo_inline() { 
   static int bar; // one copy in the entire program
}

&foo_static также будет отличаться в разных единицах перевода, тогда как &foo_inline будет таким же.

Нет способа запросить static семантику для функций-членов (даже для статических функций-членов).

Также нет способазапросить inline семантику для любой функции без ее объявления (явно или неявно) inline.Другими словами, нельзя сказать «заставить эту функцию вести себя как inline во всем, кроме фактического встраивания».

С другой стороны, семантика для функции templates аналогичнаиз inline функционирует без запроса к компилятору (как бы бессмысленно это ни было в настоящее время) встроить их в свои сайты вызовов.

// in several translation units
template <nullptr_t=nullptr>
void foo_template() { 
   static int bar; // one copy in the entire program
}
0 голосов
/ 09 июня 2018

Отметьте это inline.

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

...