Чи може декларація вплинути на простір імен std?


96
#include <iostream>
#include <cmath>

/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
    return a > 0? -a : a;
}

int main() {
    int a = abs(-5);
    int b = std::abs(-5);
    std::cout<< a << std::endl << b << std::endl;
    return 0;
}

Я очікував, що результат буде -5і 5, але результат буде -5і -5.

Цікаво, чому цей випадок трапиться?

Це має щось спільне із використанням stdчи що?


1
Ваша реалізація absнеправильна.
Річард Криттен,

31
@RichardCritten У цьому суть. OP запитує, чому додавання цього непрацездатного absвпливає std::abs().
HolyBlackCat

11
Цікаво, я отримую 5і 5з дзвінком, -5і -5з gcc.
Rakete1111

10
Cmake - це не компілятор, а швидше система збірки. Ви можете використовувати cmake для побудови за допомогою різних компіляторів.
HolyBlackCat

5
Я б, мабуть, порекомендував вам просто мати свою функцію return 0- це дозволило б уникнути того, щоб люди думали, що ви ненавмисно застосували функцію неправильно та зробили бажану та фактичну поведінку зрозумілішою.
Бернхард Баркер,

Відповіді:


92

Специфікація мови дозволяє реалізаціям реалізовувати <cmath>шляхом декларування (та визначення) стандартних функцій у глобальному просторі імен, а потім перенесення їх у простір імен stdза допомогою декларацій use. Не визначено, чи застосовується такий підхід

20.5.1.2 Заголовки
4 [...] У стандартній бібліотеці С ++, однак, оголошення (за винятком імен, які визначені як макроси в С) знаходяться в межах простору імен (6.3.6) простору імен std. Не вказано, чи спочатку ці імена (включаючи будь-які перевантаження, додані в пунктах 21 - 33 та Додатку D) спочатку оголошуються в межах загального простору імен, а потім вводяться у простір імен stdза допомогою явних декларацій use (10.3.3).

Очевидно, ви маєте справу з однією з реалізацій, яка вирішила слідувати цьому підходу (наприклад, GCC). Тобто ваша реалізація забезпечує ::abs, тоді як std::absпросто "посилається" на ::abs.

У цьому випадку залишається одне питання: чому на додаток до стандарту ::absви змогли оголосити своє ::abs, тобто чому немає помилок множинного визначення. Це може бути спричинено іншою функцією, яка надається деякими реалізаціями (наприклад, GCC): вони оголошують стандартні функції так званими слабкими символами , що дозволяє вам "замінити" їх власними визначеннями.

Ці два фактори разом створюють ефект, який ви спостерігаєте: заміна слабкого символу ::absтакож призводить до заміни std::abs. Наскільки добре це узгоджується з мовним стандартом - це вже інша історія ... У будь-якому випадку, не покладайтесь на таку поведінку - мова не гарантує.

У GCC цю поведінку можна відтворити на наступному мінімалістичному прикладі. Один вихідний файл

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

Ще один вихідний файл

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

У цьому випадку ви також помітите, що нове визначення ::foo( "Goodbye!") у другому вихідному файлі також впливає на поведінку N::foo. Виведеться обидва виклики "Goodbye!". І якщо ви видалите визначення ::fooз другого вихідного файлу, обидва виклики надішлють до "вихідного" визначення ::fooта виводу "Hello!".


Дозвіл, наданий вищезазначеним 20.5.1.2/4, існує для спрощення впровадження <cmath>. Реалізаціям можна просто включити стиль C <math.h>, а потім передекларувати функції stdта додати деякі доповнення та налаштування для C ++. Якщо вищевказане пояснення належним чином описує внутрішню механіку проблеми, то основна частина цього залежить від заміни слабких символів для версій функцій у стилі С.

Зверніть увагу, що якщо ми просто глобально замінимо intна doubleу наведеній вище програмі, код (згідно з GCC) буде поводитися "як очікувалося" - він виведе -5 5. Це трапляється, оскільки стандартна бібліотека C не має abs(double)функції. Декларуючи своє abs(double), ми нічого не замінюємо.

Але якщо після перемикання з за intдопомогою doubleми також перемикаємося з absна fabs, оригінальна дивна поведінка знову з’явиться у повній красі (вихід -5 -5).

Це узгоджується з наведеним вище поясненням.


як я бачу у джерелі cmath немає using ::abs;подібного для, using ::asin;тому Ви можете перевизначити оголошення, ще один момент, про який слід згадати, полягає в тому, що визначені в std функції простору імен не оголошуються для int, а скоріше для double , float
Take_Care_

2
З точки зору стандарту, поведінка невизначена за [extern.names] / 4 .
xskxzr

Але коли я видалив #include<cmath>код у своєму коді, я отримав ту саму відповідь.
Пітер,

@Peter Але тоді звідки ти береш std :: abs? - Це може бути включено через інше включення, після чого ви повернулися до цього пояснення. (Для компілятора не має значення, включений чи заголовок прямо чи опосередковано.)
РМ

@Peter: absможе бути оголошений <cstdlib>також, що може неявно бути включеним через <iostream>. Спробуйте видалити свій власний absі перевірити, чи все ще він компілюється.
АНТ

13

Ваш код спричиняє невизначену поведінку.

C ++ 17 [extern.names] / 4:

Кожен підпис функції зі стандартної бібліотеки C, оголошеної із зовнішнім зв'язком, зарезервований для реалізації для використання як підпис функції як із зовнішнім зв'язком "C", так і з зовнішнім зв'язком "C ++", або як ім'я області простору імен у глобальному просторі імен.

Таким чином , ви не можете створити функцію з тим же прототипом як функції стандартної бібліотеки C int abs(int);. Незалежно від того, які заголовки ви насправді включаєте, чи ці заголовки також містять імена бібліотек C у глобальному просторі імен.

Однак дозволено перевантажувати, absякщо ви надаєте різні типи параметрів.


1
"або як ім'я простору імен у глобальному просторі імен", тому його не можна перевантажувати в глобальному просторі імен.
xskxzr

@xskxzr Я не впевнений у тлумаченні тексту, який ви цитуєте; якщо це прийнято означати, що користувач не може оголосити нічого з цього імені у глобальному просторі імен, то попередня частина тексту, яку я цитував, буде зайвою, як і більшість [extern.names] / 3. Що змушує мене думати, що тут задумано щось інше.
М.М.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.