當我獲得成員函式的地址時,我無法將地址分配給 void*:
void* ptr = &object::function; // <-- doesn't work!
我知道這是因為成員函式為您提供了這些指向成員的奇怪指標,而不僅僅是表現得像一個普通函式,但我不明白為什么會這樣。
是的,該函式有一個 this 指標的額外引數。是的,該函式在相關物件的背景關系之外并沒有真正意義。但最終,該函式仍然位于特定的記憶體地址,就像那里的所有其他函式一樣。考慮到這一點,我不明白為什么語言設計者讓我跳過箍只是為了獲取成員函式的記憶體地址:
對于正常功能:
void function() { }
std::cout << (size_t)&function << '\n';
不需要有趣的業務,但對于成員函式:
struct object {
void function() { }
}
void object::* ptr = &object::function;
std::cout << *(size_t*)&ptr << '\n';
它似乎沒有理由變得奇怪。我有點理解成員變數背景關系中指向成員的指標,在創建實體之前它們實際上沒有地址,所以在此之前你使用指向成員的指標作為一種偽指標。但我不明白指向成員函式的指標。它們仍然是函式,為什么它們會得到這種奇怪的特殊處理,這些指向成員函式的指標有什么實際優勢?
uj5u.com熱心網友回復:
我無法將地址分配給 void*
但我不明白為什么會這樣。
最直接的解釋是指向成員函式的指標不能轉換為void*. 最直接的解釋是語言不允許。指向成員的指標不是指標(這適用于指向資料成員的指標以及指向成員函式的指標)。
該函式仍在特定的記憶體地址
這不是那么簡單。如果成員函式是虛擬的,實際上可能有許多實作,并且每個實作將在不同的記憶體地址中。語言實作必須以某種方式確保當您通過指向派生物件上的成員函式的指標呼叫時,會呼叫正確的覆寫,其正確地址this不一定是實體引數的地址。當涉及多重繼承時,這變得更加復雜。指向成員函式的指標通常包含比單個地址更多的資訊。
如果您sizeof在語言實作中檢查指向成員函式的指標,那么您可能會發現它大于void*. 轉換為的任何內容都void*必須可轉換回相同的值,當原始型別的狀態大于void*可以表示的狀態時,這是不可能的。
旁注 1:從指標到函式的轉換void*是有條件支持的功能,因此在所有(舊的、深奧的?)語言實作上也可能無法實作。
旁注2:
std::cout << (size_t)&function << '\n';
這不僅依賴于前面提到的條件特征,而且還依賴于從void*到std::size_t定義良好的轉換。這也不能保證。當您想將指標轉換為整數時,您應該更喜歡使用std::uintptr_t。
旁注3:
void object::* ptr = &object::function;
這是不正確的。它看起來像一個指向 type 資料成員的指標void,這當然是不允許的。我想你打算寫:
void (object::* ptr)() = &object::function;
旁注4:
*(size_t*)&ptr
&ptr指向 type 的物件void (object::*)(),因此將其重新解釋為size_t*并通過重新解釋的指標訪問指向的物件會導致未定義的行為。不要在真正的程式中這樣做。
uj5u.com熱心網友回復:
請注意,根據 C 和 C 規范,您不能將任何函式指標強制轉換為void*. 它是一個常見的擴展,允許它用于普通函式(并且該擴展是 POSIX 標準所要求的,因此很可能仍然很常見),但對于成員函式指標而言則不是那么多。
原因是成員函式的許多實作除了呼叫函式實作之外還需要額外的修復/查找——對于虛函式,您需要根據動態型別this(通常是 vtable 查找)查找實作,并支持多個繼承可能需要this在呼叫函式之前進行一些調整。如此多的實作使用大于 avoid *且無法放入其中的方法指標表示。
uj5u.com熱心網友回復:
但最終,該函式仍然位于特定的記憶體地址,就像那里的所有其他函式一樣。
錯誤的。(嗯,確實每個函式都有一個特定的地址,但是對于指向成員函式的特定值總是有一個“函式”的想法使得這個錯誤。)
指向成員的指標不存盤特定的記憶體地址。相反,它存盤的東西可以與正確型別的物件組合以產生特定的記憶體地址。
例如,以下代碼
#include <iostream>
// Define some types to use in the example
struct Base {
void function() { std::cout << "Function\n"; }
virtual void virtualFunction() { std::cout << "Base\n"; }
};
struct Derived : Base {
void virtualFunction() override { std::cout << "Derived\n"; }
};
int main()
{
Base base;
Derived derived;
// Demonstrate pointing to a virtual function.
void (Base::*pointer)() = &Base::virtualFunction;
(base.*pointer)();
(derived.*pointer)();
// Demonstrate pointing to a non-virtual function.
pointer = &Base::function;
(base.*pointer)();
(derived.*pointer)();
}
產生以下輸出
Base
Derived
Function
Function
變數被呼叫的前兩次pointer,兩個不同的函式被呼叫,即使pointer沒有改變。這表明指向成員函式的指標不能簡單地保存單個函式的地址。它所指向的可能取決于它所結合的物件。
這個例子的第二部分是為了證明同一個變數同樣能夠持有一個指向非虛函式的指標。相同的型別,甚至相同的變數,都需要能夠處理需要動態調度的情況和不需要動態調度的情況。僅存盤非虛擬成員函式的地址是不夠的,因為該值還必須包含“我不指向虛擬函式”的資訊。
我傾向于將指向成員的指標視為偏移量。指向資料成員的指標可以實作為物件的偏移量,而指向成員函式的指標可以實作為虛擬函式表的偏移量,其中虛擬表被視為擴展為還包含非虛成員函式。如果您擁有可接受型別的物件(的地址),則可以(由編譯器)使用這些偏移量來獲取特定地址。
請記住,將指向成員的指標視為偏移量只是一種方法(在處理成員模板時并不是一種特別好的方法)。其他方法是可能的,特別是如果sizeof(pointer)恰好是兩次sizeof(std::ptrdiff_t)。因此,不要試圖根據這種觀點得出結論一定會發生某些事情。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/484911.html
