Разрешение перегрузки с внешней "C" связью - PullRequest
11 голосов
/ 01 февраля 2011

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

Есть два способа сделать это: (1) написать небольшую оболочку C ++ с внешней функцией «C», которая перенаправляет вызов выбранной перегруженной функции, или (2) хакерский способ просто объявить одну функцию, которую мы хотим звонить с C как extern "C".

Вопрос в том, есть ли какие-либо недостатки (кроме кошмаров и плохой кармы) для второго варианта? Другими словами, учитывая три перегруженные функции, где одна объявлена ​​как exern "C", следует ли ожидать проблем со стороной C ++, или это хорошо определено в соответствии со стандартом?

Ответы [ 4 ]

18 голосов
/ 01 февраля 2011

Я полагаю, что язык в стандарте специально написан, чтобы разрешить ровно одну функцию со связью "C" и произвольное число других функций со связью "C ++", которые перегружают одно и то же имя (§ [dcl.link] / 6 ):

Не более одной функции с конкретным именем может иметь связь на языке Си. Два объявления для функции со связью языка C с одним и тем же именем функции (игнорируя имена пространств имен, которые ее определяют), которые появляются в разных областях имен, относятся к одной и той же функции. Два объявления для объекта со связью языка C с одним и тем же именем (игнорируя имена пространств имен, которые его определяют), которые появляются в разных областях пространства имен, ссылаются на один и тот же объект.

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

complex sqrt(complex); // C + + linkage by default
extern "C" {
    double sqrt(double); // C linkage
}
2 голосов
/ 01 февраля 2011

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

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

namespace your_project {  // You do use one, right? :)
  void f(int x);
  void f(char x);
  void f(other_overloads x);
}

extern "C"
void f(int x) {
  your_project::f(x);
}
2 голосов
/ 01 февраля 2011

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

Просто напишите обертку.

EDIT: Из C ++ 03 7.5 / 5:

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

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

Затем из C ++ 03 7.5 / 6:

Не более одной функции с определенной имя может иметь связь с языком C ...

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

1 голос
/ 09 мая 2016

(Этот ответ относится к C ++ 14; на данный момент это C ++ 03).

Разрешено использовать перегрузку.Если существует определение функции extern "C" для определенного имени, применяются следующие условия (ссылки на C ++ 14 в скобках):

  • Объявление функции extern "C" должно быть видно по адресусмысл любого объявления или определения перегрузок этого имени функции (7.5 / 5)
  • Не должно быть никакого другого определения extern "C" функции или переменной с тем же именем, где бы то ни было.(7.5 / 6)
  • Перегруженная функция с тем же именем не должна объявляться в глобальной области видимости.(7.5 / 6)
  • В том же пространстве имен, что и у функции extern "C", не должно быть другого объявления функции с тем же именем и списком параметров.(7.5 / 5)

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

Таким образом, ваш заголовочный файл может выглядеть примерно так:

namespace foo
{
    extern "C" void bar();
    void bar(int);
    void bar(std::string);
}

В последнем пуле говорится, что вы не можете перегружать только при связывании;это неправильно:

namespace foo
{
    extern "C" void bar();
    void bar();               // error
}

Однако вы можете сделать это в разных пространствах имен:

extern "C" void bar();
namespace foo
{
    void bar();
}

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

...