我很難弄清楚為什么我的代碼不起作用。我將問題追溯到 vtable 生成/人口。
這是我的代碼的簡化版本:
#include <iostream>
#include <functional>
#include <thread>
using namespace std;
typedef std::function<void (void)> MyCallback;
MyCallback clb = NULL;
void callCallbackFromThread(void){
while(clb == NULL);
clb();
}
int someLongTask(void){
volatile unsigned int i = 0;
cout << "Start someLongTask" << endl;
while(i < 100000000)
i ;
cout << "End someLongTask" << endl;
return i;
}
class Base{
public:
Base(){
clb = std::bind(&Base::_methodToCall, this);
cout << "Base-Constructor" << endl;
}
protected:
virtual void methodToCall(void){
cout << "Base methodToCall got called!" << endl;
};
private:
void _methodToCall(void){
methodToCall();
}
};
class Child: public Base{
public:
Child()
: dummy(someLongTask()){
cout << "Child Constructor" << endl;
}
protected:
void methodToCall(void){
cout << "Child methodToCall got called!" << endl;
}
private:
int dummy;
};
int main()
{
thread t(callCallbackFromThread);
Child child;
t.join();
clb();
return 0;
}
當我運行它時,有時會得到結果:
Base-Constructor
Start someLongTask
Child methodToCall got called!
End someLongTask
Child Constructor
Child methodToCall got called!
有時是這樣的:
Base-ConstructorBase methodToCall got called!
Start someLongTask
End someLongTask
Child Constructor
Child methodToCall got called!
有人可以向我解釋為什么在一種情況下呼叫基本方法而在另一種情況下呼叫子方法嗎?它肯定與執行緒執行的確切時間有關。對我來說,似乎當執行緒呼叫回呼時 vtable 沒有正確設定,但我不明白為什么。
uj5u.com熱心網友回復:
vtable 是 C 語言的一個實作細節。如果您遵循語言規則并且不深入研究未定義的行為領域以在特定語言實作中進行破解,那么您永遠不必關心它。
您的代碼失敗,因為您沒有使用多執行緒程式所需的任何適當同步。
clb不是原子變數,因此不允許您在一個執行緒中存盤它并在另一個執行緒中從它加載而不使用一些同步原語,例如保護讀寫訪問的互斥鎖。
在多個執行緒中訪問非原子物件而不同步其中至少一個是讀取被稱為資料競爭,并且在 C 中是無條件未定義的行為。
正如@Mat 所指出的,呼叫clb將使用的類物件的生命周期還有另一場競賽。構造期間的虛函式呼叫只允許直接或間接地從通過建構式的路徑中呼叫。這不適用于執行緒中的呼叫,并且也沒有同步來確保類物件的所有建構式在clb呼叫之前都已完成。因此再次未定義的行為。(這通俗地稱為 vtable 指標上的資料競爭,因為這就是它在實踐中失敗的原因。)
uj5u.com熱心網友回復:
撇開資料競爭不談,當派生類物件在 C 中構造時,它從 Base 物件開始,然后轉換為 Child 物件。(如果您有多個繼承級別,則物件轉換不止一次)
如果從基類建構式呼叫虛函式,會看到呼叫了基類實作,因為物件仍然是基類物件。
_methodToCall();如果您在基類建構式中呼叫,您所看到的行為正是您所期望的。在您的代碼中,呼叫發生在另一個執行緒上,而基類建構式仍在運行,這帶來了無數的風險,但執行緒實際上是無關緊要的 - 您可以簡單地在基類建構式中撰寫_methodToCall();或clb();觀察類似的行為。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/504228.html
