簡體   English   中英

當非常量方法是私有的時,為什么不調用公共常量方法?

[英]Why is a public const method not called when the non-const one is private?

考慮這個代碼:

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

編譯器錯誤是:

錯誤:“void A::foo()”是私有的。

但是當我刪除私人的時,它就起作用了。 當非常量方法是私有的時,為什么不調用公共 const 方法?

換句話說,為什么重載決議先於訪問控制? 這很奇怪。 你認為它是一致的嗎? 我的代碼可以工作,然后我添加了一個方法,但我的工作代碼根本無法編譯。

當你調用a.foo(); ,編譯器通過重載決議來找到最好的函數來使用。 當它構建重載集時,它發現

void foo() const

void foo()

現在,由於a不是const ,非常量版本是最佳匹配,因此編譯器選擇void foo() 然后訪問限制就位,你會得到一個編譯器錯誤,因為void foo()是私有的。

請記住,在重載決議中,它不是“找到最佳可用函數”。 它是“找到最好的功能並嘗試使用它”。 如果由於訪問限制或被刪除而無法訪問,則會出現編譯器錯誤。

換句話說,為什么重載決議先於訪問控制?

好吧,讓我們來看看:

struct Base
{
    void foo() { std::cout << "Base\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived\n"; }
};

struct Foo
{
    void foo(Base * b) { b->foo(); }
private:
    void foo(Derived * d) { d->foo(); }
};

int main()
{
    Derived d;
    Foo f;
    f.foo(&d);
}

現在讓我們說我實際上並不是想將void foo(Derived * d)設為私有。 如果訪問控制首先出現,那么該程序將編譯並運行,並且Base將被打印。 這可能很難在大型代碼庫中進行追蹤。 由於訪問控制是在重載解析之后出現的,我得到一個很好的編譯器錯誤,告訴我我想要它調用的函數無法調用,我可以更容易地找到錯誤。

最終這歸結為標准中的斷言,即在執行重載解析時不應考慮可訪問性 這個斷言可以在[over.match]條款 3 中找到:

... 當重載決議成功時,最佳可行函數在使用它的上下文中不可訪問(條款 [class.access]),則程序格式錯誤。

以及同一節第 1 條中的注釋

[注意:重載解析選擇的函數不保證適合上下文。 其他限制,例如函數的可訪問性,可以使其在格式錯誤的調用上下文中使用。 — 尾注 ]

至於為什么,我可以想到幾個可能的動機:

  1. 它可以防止由於更改重載候選的可訪問性而導致的意外行為更改(相反,將發生編譯錯誤)。
  2. 它從重載解析過程中移除了上下文相關性(即,無論是在類內部還是外部,重載解析都將具有相同的結果)。

假設訪問控制出現在重載決議之前。 實際上,這意味着public/protected/private控制的可見性而不是可訪問性。

StroustrupDesign and Evolution of C++ 的第 2.10 節有一段關於這方面的內容,他討論了以下示例

int a; // global a

class X {
private:
    int a; // member X::a
};

class XX : public X {
    void f() { a = 1; } // which a?
};

Stroustrup 提到當前規則的一個好處(可訪問性之前的可見性)是(暫時)將private內部class X更改為public (例如出於調試目的)是上述程序的含義沒有安靜的變化(即在這兩種情況下都嘗試訪問X::a ,這在上面的示例中給出了訪問錯誤)。 如果public/protected/private將控制可見性,程序的含義就會改變(全局a將使用private調用,否則為X::a )。

然后他表示,他不記得這是通過顯式設計還是用於實現 C with Classess 標准 C++ 前身的預處理器技術的副作用。

這與你的例子有什么關系? 基本上是因為標准使重載解析符合名稱查找先於訪問控制的一般規則。

10.2 成員名稱查找[class.member.lookup]

1 成員名稱查找確定類范圍 (3.3.7) 中名稱(id 表達式)的含義。 名稱查找可能會導致歧義,在這種情況下,程序格式錯誤。 對於 id 表達式,名稱查找從 this 的類范圍開始; 對於qualified-id,名稱查找在nestedname-specifier 的范圍內開始。 名稱查找發生在訪問控制之前(3.4,第 11 條)。

8 如果明確找到重載函數的名稱,重載解析 (13.3) 也會在訪問控制之前發生 歧義通常可以通過用類名限定名稱來解決。

由於隱式this指針是非const ,編譯器將首先檢查的非存在const功能的版本之前const版本。

如果您顯式地將非const標記為private則解析將失敗,並且編譯器將不會繼續搜索。

記住事情發生的順序很重要,即:

  1. 找到所有可行的功能。
  2. 選擇最好的可行函數。
  3. 如果不完全有一個最佳可行函數,或者如果您實際上無法調用最佳可行函數(由於訪問沖突或函數被delete d),則失敗。

(3) 發生在 (2) 之后。 這真的很重要,因為否則使函數delete d 或private將變得毫無意義並且更難以推理。

在這種情況下:

  1. 可行的函數是A::foo()A::foo() const
  2. 最好的可行函數是A::foo()因為后者涉及對隱式this參數的限定轉換。
  3. 但是A::foo()private ,您無權訪問它,因此代碼格式錯誤。

這歸結為 C++ 中相當基本的設計決策。

當查找函數以滿足調用時,編譯器執行如下搜索:

  1. 它搜索找到的第1個范圍,在其中有什么東西與這個名字。

  2. 編譯器在該作用域內找到所有具有該名稱的函數(或函子等)。

  3. 然后編譯器進行重載解析以在它找到的那些中找到最佳候選者(無論它們是否可訪問)。

  4. 最后,編譯器檢查所​​選函數是否可訪問。

由於這種順序,是的,編譯器可能會選擇一個不可訪問的重載,即使還有另一個可訪問的重載(但在重載解析期間未選擇)。

至於是否有可能以不同的方式做事:是的,這無疑是可能的。 不過,它肯定會導致與 C++ 完全不同的語言。 事實證明,許多看似微不足道的決定可能會產生比最初顯而易見的影響更大的后果。


  1. “第一”本身可能有點復雜,尤其是當/如果涉及模板時,因為它們可能導致兩階段查找,這意味着在進行搜索時有兩個完全獨立的“根”要開始。 不過,基本思想非常簡單:從最小的封閉范圍開始,然后逐步擴展到越來越大的封閉范圍。

訪問控制( publicprotectedprivate )不影響重載解析。 編譯器選擇void foo()因為它是最佳匹配。 它不可訪問的事實並沒有改變這一點。 刪除它只留下void foo() const ,這是最好的(即唯一的)匹配。

在這次通話中:

a.foo();

每個成員函數中總是有一個隱式的this指針可用。 並且thisconst限定來自調用引用/對象。 上述調用編譯器視為:

A::foo(a);

但是您有兩個A::foo聲明,其處理方式如下

A::foo(A* );
A::foo(A const* );

通過重載決議,第一個將被選擇為非常量this ,第二個將被選擇為一個const this 如果刪除第一個,第二個將綁定到constnon-const this

在重載決議以選擇最佳可行功能之后,訪問控制。 由於您將所選重載的訪問權限指定為private ,因此編譯器會抱怨。

標准是這樣說的:

[class.access/4] : ...在重載函數名的情況下,訪問控制應用於重載解析選擇的函數....

但如果你這樣做:

A a;
const A& ac = a;
ac.foo();

然后,只適合const重載。

其他答案已經回答了技術原因。 我只關注這個問題:

換句話說,為什么重載決議先於訪問控制? 這很奇怪。 你認為它是一致的嗎? 我的代碼有效,然后我添加了一個方法,但我的工作代碼根本無法編譯。

這就是語言的設計方式。 目的是盡可能地調用最佳可行的重載。 如果失敗,則會觸發錯誤,提醒您重新考慮設計。

另一方面,假設您的代碼已編譯並與被調用的const成員函數配合良好。 有一天,有人(可能是您自己)然后決定將非const成員函數的可訪問性從private更改為public 然后,行為會改變而不會出現任何編譯錯誤! 這將是一個驚喜

因為main函數中的變量a沒有聲明為const

在常量對象上調用常量成員函數。

訪問說明符永遠不會影響名稱查找和函數調用解析。 在編譯器檢查調用是否應觸發訪問沖突之前選擇該函數。

這樣,如果您更改訪問說明符,如果現有代碼中存在違規,您將在編譯時收到警報; 如果在函數調用解析中考慮了隱私,您的程序的行為可能會悄然改變。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM