2012-08-09 46 views
64

在下面的代碼中,我定義了一個簡單的log函數。在main我嘗試而不是來調用它;我致電std::log。儘管如此,我自己的log被稱爲;我看到「登錄!」在屏幕上。有誰知道爲什麼?我使用G ++ 4.7和clang ++ 3.2。爲什麼我在std命名空間登錄?

#include <iostream> 
#include <cmath> 

double log(const double x) { std::cout << "log!\n"; return x; } 

int main(int argc, char *argv[]) 
{ 
    std::log(3.14); 
    return 0; 
} 
+10

聽起來像一個嚴重的編譯器缺陷... – MFH 2012-08-09 22:38:53

+0

我的MacPorts下重現此對G ++ 4.6。儘管如此,它在g ++ 4.2或4.4中不會發生。 – carlosdc 2012-08-09 22:39:24

+1

http://codepad.org/Uwhgrv7q http://codepad.org/z07Cffyn Frome這兩個我會說std :: log()函數調用log()。但它會產生一個錯誤/警告你的文件重新定義日誌或類似的東西 – Gir 2012-08-09 22:42:33

回答

53

C++標準17.6.1.2第4(重點煤礦):

除了通過30和附件d如項18所指出的,每一個報頭cname的內容應是相同的與對應的標題的如C標準庫(1.2)或C Unicode TR中所規定的那樣,根據需要包括在內。然而,在C++標準庫中,聲明(在C中定義爲宏的名稱除外)位於名稱空間std的名稱空間範圍(3.3.6)內。 未指定這些名稱是否首先在全局名稱空間範圍內聲明,然後通過顯式使用聲明(7.3.3)將其注入名稱空間std

g ++這樣做後一種方式,以便一些相同的頭文件可以重用於C和C++。因此,允許g ++在全局名稱空間中聲明並定義double log(double)

第17.6.4.3.3第3和4:

從外部鏈接的聲明的標準C庫中的每個名稱被保留,以用作與extern "C"聯動名稱的實施,無論是在命名空間std和全局命名空間中。

從外部鏈接的聲明的標準C庫的每個功能的簽名被保留到實施作爲函數簽名的使用既extern "C"extern "C++"聯動,或命名空間範圍的名稱在全局命名空間。

,擡頭審視第17.6.4.3頂部第2款:

如果程序聲明或在它是保留一個上下文定義了一個名字,除了由於明確本條款允許的,它的行爲是不確定的。

你,在另一方面,可能聲明或以任何方式限定::log

雖然g ++工具鏈不會給你任何錯誤信息,但這太糟糕了。

+6

+1:這是最好的答案。 – 2012-08-09 23:08:43

8

會發生什麼事,我想到,是std::log只是委託給::log。不幸的是,::log只提供了一個float過載,並且您提供一個double過載,使您更好地匹配。但我仍然沒有看到它在超負荷情況下如何被考慮。

+0

''需要提供'log(double)'。 – aschepler 2012-08-09 22:48:02

+1

我期望在MSVC10上也是如此,我最終在鏈接上得到了一個重複的符號錯誤,但是隻有在重建3或4之後。 – 2012-08-09 22:49:23

+0

@aschepler:''需要提供'雙重std :: log(double)'(我將注意力放在命名空間上是一件大事,因爲它現在是正確的)。該標準要求它們位於':: std'名稱空間中,並且每個實現都可以在全局名稱空間中自由定義它們(如果它們是這樣選擇的話)。 – Cornstalks 2012-08-09 22:59:03

8

上的libstdC++的cmath你會看到:

using ::log; 

因此它帶來的math.h中的功能從全局命名空間爲std。不幸的是,你正在爲double log(double)提供一個實現,因此鏈接器不會使用數學庫中的一個。 所以絕對是libstdC++ 中的一個bug。

編輯:我聲稱這是libstdC++中的一個錯誤,因爲std::log在明確要求std::版本時不應受到與C庫的干擾。當然,這種覆蓋標準庫函數的方式是來自C語言的舊「特性」。編輯2:我發現該標準實際上並不禁止將名稱從全局名稱空間帶入std。所以不是一個錯誤,只是實現細節的一個後果。

+0

'using'聲明應該只導入那個時候可見的聲明,我認爲這意味着後面聲明的函數不應該以這種方式導入。 – bames53 2012-08-09 22:55:24

+0

我編輯了我的答案,標準允許這種類型的實現,它首先聲明math.h函數,然後將'double'版本帶入'std'。 – DanielKO 2012-08-09 23:09:44

6

在C++中,編譯器可以自由地在全局名稱空間中實現C庫並委託給它(這是實現定義的)。

17.6.1.2.4通過除30和附件d如項18所指出的,每一個報頭的CNAME的內容應是相同的 作爲相應的頭name.h的,如在C標準SPECI音響編庫(1.2)或C Unicode TR,視情況而定,如同包含在內。然而,在C++標準庫中,聲明(在C中被定義爲宏的 名稱除外)位於命名空間std的名稱空間範圍(3.3.6)內。 這是 未指定這些名稱是否首先在全局命名空間範圍內聲明,然後通過顯式使用聲明(7.3.3)將 注入到命名空間std中。

一般來說,我會避免使用與C標準庫之一相同的簽名來製作函數。 C++標準肯定會給編譯器自由地使用這些簽名(如果它選擇的話),這意味着如果您嘗試使用相同的簽名,則可能會與編譯器發生衝突。因此,你會得到奇怪的結果。

雖然我會期待鏈接器錯誤或警告,我認爲這可能值得報告。

哇,忍者。

+0

下次更好運氣:) – user2023370 2012-08-10 08:37:00

0

因爲您已在全局命名空間中重寫它。例如,如果您不希望轉到更安全,更乾淨的語言(如Nim),則使用名稱空間可避免此危險。

Proper use of namespace demo

#include <iostream> 
#include <cmath> // Uses ::log, which would be the log() here if it were not in a namespace, see http://stackoverflow.com/questions/11892976/why-is-my-log-in-the-std-namespace 

// Silently overrides std::log 
//double log(double d) { return 420; } 

namespace uniquename { 
    using namespace std; // So we don't have to waste space on std:: when not needed. 

    double log(double d) { 
     return 42; 
    } 

    int main() { 
     cout << "Our log: " << log(4.2) << endl; 
     cout << "Standard log: " << std::log(4.2); 
     return 0; 
    } 
} 

// Global wrapper for our contained code. 
int main() { 
    return uniquename::main(); 
} 

輸出:

Our log: 42 
Standard log: 1.43508 
相關問題