Đặc tả ngôn ngữ cho phép triển khai thực hiện <cmath>
bằng cách tuyên bố (và xác định) các chức năng tiêu chuẩn trong toàn cầu namespace và sau đó đưa chúng vào namespace std
bằng phương tiện của việc sử dụng-khai. Không xác định được liệu phương pháp này có được sử dụng hay không
20.5.1.2 Headers
4 [...] Tuy nhiên, trong thư viện chuẩn C ++, các khai báo (ngoại trừ các tên được định nghĩa là macro trong C) nằm trong phạm vi vùng tên (6.3.6) của vùng tên std
. Không xác định được liệu các tên này (bao gồm bất kỳ quá tải nào được bổ sung trong các Khoản từ 21 đến 33 và Phụ lục D) lần đầu tiên được khai báo trong phạm vi không gian tên chung và sau đó được đưa vào không gian tên std
bằng cách sử dụng khai báo rõ ràng (10.3.3).
Rõ ràng, bạn đang xử lý một trong các triển khai quyết định theo cách tiếp cận này (ví dụ: GCC). Tức là triển khai của bạn cung cấp ::abs
, trong khi std::abs
chỉ đơn giản là "tham chiếu" đến ::abs
.
Một câu hỏi vẫn còn trong trường hợp này là tại sao ngoài tiêu chuẩn ::abs
bạn có thể khai báo của riêng mình ::abs
, tức là tại sao không có lỗi nhiều định nghĩa. Điều này có thể do một tính năng khác được cung cấp bởi một số triển khai (ví dụ: GCC): chúng khai báo các hàm tiêu chuẩn được gọi là ký hiệu yếu , do đó cho phép bạn "thay thế" chúng bằng định nghĩa của riêng bạn.
Hai yếu tố này kết hợp với nhau tạo ra hiệu ứng mà bạn quan sát: sự thay thế ký hiệu yếu của ::abs
cũng dẫn đến sự thay thế của std::abs
. Điều này phù hợp với tiêu chuẩn ngôn ngữ đến mức nào là một câu chuyện khác ... Trong mọi trường hợp, đừng dựa vào hành vi này - nó không được đảm bảo bởi ngôn ngữ.
Trong GCC, hành vi này có thể được tái tạo bằng ví dụ tối giản sau. Một tệp nguồn
#include <iostream>
void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }
Một tệp nguồn khác
#include <iostream>
void foo();
namespace N { using ::foo; }
void foo() { std::cout << "Goodbye!" << std::endl; }
int main()
{
foo();
N::foo();
}
Trong trường hợp này, bạn cũng sẽ thấy rằng định nghĩa mới của ::foo
( "Goodbye!"
) trong tệp nguồn thứ hai cũng ảnh hưởng đến hành vi của N::foo
. Cả hai cuộc gọi sẽ xuất ra "Goodbye!"
. Và nếu bạn loại bỏ định nghĩa của::foo
khỏi tệp nguồn thứ hai, cả hai lệnh gọi sẽ chuyển đến định nghĩa ::foo
và đầu ra "gốc" "Hello!"
.
Sự cho phép được đưa ra bởi 20.5.1.2/4 ở trên là có để đơn giản hóa việc triển khai <cmath>
. Việc triển khai chỉ được phép bao gồm C-style <math.h>
, sau đó khai báo lại các hàm trongstd
và thêm một số bổ sung và chỉnh sửa cụ thể của C ++. Nếu giải thích ở trên mô tả đúng cơ chế bên trong của vấn đề, thì một phần chính của nó phụ thuộc vào khả năng thay thế của các ký hiệu yếu đối với các phiên bản kiểu C của các hàm.
Lưu ý rằng nếu chúng ta chỉ thay thế toàn cục int
bằng double
trong chương trình trên, mã (trong GCC) sẽ hoạt động "như mong đợi" - nó sẽ xuất-5 5
. Điều này xảy ra vì thư viện chuẩn C không có abs(double)
chức năng. Bằng cách khai báo riêng của abs(double)
chúng tôi, chúng tôi không thay thế bất cứ điều gì.
Nhưng nếu sau khi chuyển từ int
với double
chúng tôi cũng chuyển từ abs
sangfabs
, hành vi kỳ lạ ban đầu sẽ xuất hiện lại trong vinh quang đầy đủ của nó (đầu ra -5 -5
).
Điều này phù hợp với giải thích trên.
abs
không chính xác.