การประกาศมีผลต่อเนมสเปซมาตรฐานหรือไม่


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ไม่ถูกต้อง
Richard Critten

31
@RichardCritten นั่นคือประเด็น OP ถามว่าทำไมการเพิ่มabsผลเสียstd::abs()นี้
HolyBlackCat

11
น่าสนใจฉันได้รับ5และ5มีเสียงดัง-5และ-5ด้วย gcc
Rakete1111

10
Cmake ไม่ใช่คอมไพเลอร์ แต่เป็นระบบบิลด์ คุณสามารถใช้ cmake เพื่อสร้างด้วยคอมไพเลอร์ต่างๆ
HolyBlackCat

5
ฉันอาจจะแนะนำให้คุณมีฟังก์ชั่นของคุณreturn 0- ซึ่งจะหลีกเลี่ยงไม่ให้คนคิดว่าคุณใช้ฟังก์ชันนี้โดยไม่ได้ตั้งใจและทำให้พฤติกรรมที่ต้องการและเป็นจริงชัดเจนขึ้น
Bernhard Barker

คำตอบ:


92

ข้อกำหนดภาษาอนุญาตให้นำไปใช้งานได้<cmath>โดยการประกาศ (และกำหนด) ฟังก์ชันมาตรฐานในเนมสเปซส่วนกลางจากนั้นนำเข้าสู่เนมสเปซstdโดยใช้การประกาศ ไม่มีการระบุว่าใช้แนวทางนี้หรือไม่

20.5.1.2 ส่วนหัว
4 [ ... ] ในห้องสมุดมาตรฐาน c ++ อย่างไรก็ตามการประกาศ (ยกเว้นสำหรับชื่อซึ่งจะถูกกำหนดเป็นแมโครใน C) อยู่ในขอบเขต namespace (6.3.6) ของการ stdnamespace ไม่มีการระบุว่าชื่อเหล่านี้ (รวมถึงการโอเวอร์โหลดใด ๆ ที่เพิ่มในข้อ 21 ถึง 33 และภาคผนวก D) ถูกประกาศครั้งแรกภายในขอบเขตเนมสเปซส่วนกลางจากนั้นจะถูกฉีดเข้าไปในเนมสเปซstdโดยใช้การประกาศอย่างชัดเจน (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-style <math.h>จากนั้นประกาศฟังก์ชันใหม่stdและเพิ่ม C ++ - การเพิ่มและปรับแต่งเฉพาะบางอย่าง หากคำอธิบายข้างต้นอธิบายกลไกภายในของปัญหาอย่างถูกต้องส่วนสำคัญจะขึ้นอยู่กับความสามารถในการเปลี่ยนสัญลักษณ์ที่อ่อนแอสำหรับฟังก์ชันเวอร์ชัน 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 มาจากไหน? - อาจมีการรวมไว้ในการรวมอื่น ณ จุดนี้คุณจะกลับไปที่คำอธิบายนี้ (ไม่สำคัญว่าคอมไพเลอร์จะรวมส่วนหัวไว้โดยตรงหรือโดยอ้อม)
RM

@ Peter: absสามารถประกาศในเช่นกันซึ่งอาจรวมโดยปริยายผ่าน<cstdlib> <iostream>ลองลบของคุณเองabsและดูว่ายังรวบรวมอยู่ไหม
AnT

13

รหัสของคุณทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด

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

ลายเซ็นของฟังก์ชันแต่ละรายการจากไลบรารีมาตรฐาน C ที่ประกาศด้วยการเชื่อมโยงภายนอกจะสงวนไว้สำหรับการนำไปใช้งานเพื่อใช้เป็นลายเซ็นฟังก์ชันที่มีทั้งลิงก์ "C" ภายนอกและ "C ++" ภายนอกหรือเป็นชื่อของขอบเขตเนมสเปซในเนมสเปซส่วนกลาง

คุณจึงไม่สามารถสร้างฟังก์ชันที่มีต้นแบบเดียวกับฟังก์ชันไลบรารีมาตรฐาน C int abs(int);ได้ ไม่ว่าคุณจะใส่ส่วนหัวใดจริงหรือส่วนหัวเหล่านั้นใส่ชื่อไลบรารี C ลงในเนมสเปซส่วนกลาง

อย่างไรก็ตามจะได้รับอนุญาตให้โอเวอร์โหลดabsหากคุณระบุประเภทพารามิเตอร์ที่แตกต่างกัน


1
"หรือเป็นชื่อของขอบเขตเนมสเปซในเนมสเปซส่วนกลาง" ดังนั้นจึงไม่สามารถโอเวอร์โหลดในเนมสเปซส่วนกลางได้
xskxzr

@xskxzr ฉันไม่แน่ใจเกี่ยวกับการตีความข้อความที่คุณอ้าง หากหมายความว่าผู้ใช้ไม่สามารถประกาศชื่อใด ๆ ในเนมสเปซส่วนกลางข้อความส่วนก่อนหน้านี้ที่ฉันยกมาจะซ้ำซ้อนเช่นเดียวกับ [extern.names] / 3 ส่วนใหญ่ ซึ่งทำให้ฉันคิดว่ามีจุดมุ่งหมายอย่างอื่นที่นี่
MM
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.