2014-10-16 89 views
12

首先,我定義了兩個相互繼承的類。將std :: function <void(Derived *)>轉換爲std :: function <void(Base*)>

class A { 
}; 
class B : public A { 
}; 

然後,我宣佈一個使用std::function<void(A*)>功能:

void useCallback(std::function<void(A*)> myCallback);

最後,我收到的std::function不同的(但理論上兼容)從別的地方,我想打字在我的回調函數中使用:

std::function<void(B*)> thisIsAGivenFunction; 

useCallback(thisIsAGivenFunction); 

我的編譯器(鐺++)拒絕這個,因爲類型與預期類型不匹配。但從A繼承的BthisIsAGivenFunction是可以接受的。

應該是?如果不是,爲什麼?如果它應該,那麼我做錯了什麼?

+1

可能重複[C++模板多態](http://stackoverflow.com/questions/2203388/c-templates-polymorphism) – Samuel 2014-10-16 11:50:16

+3

它不重複,'std :: function'支持這種*轉換*。但換句話說,您可以將'std :: function '轉換爲'std :: function ',因爲在'A *'上運行的函數在接收到'B *'的實例時可以做同樣的事情,但是而不是其他方式 – 2014-10-16 11:51:20

+0

問題的根源在於使用'std :: function'作爲回調函數,而不是接受'Functor'作爲模板參數。除非你有充分的理由這樣做,否則不要這樣做。 – pmr 2014-10-16 17:28:55

回答

2

當有人可能會傳遞給您隨機Fruit包括Pear時,您無法通過&Foo(Apple)

+0

我不明白。在我的情況下,我的函數'useCallback'需要一個隨機的'Fruit',當我傳遞一個'Pear'時,編譯器會抱怨。 – Ecco 2014-10-16 11:53:50

+1

@Ecco。我認爲這是你的'useCallback'期望用一個'Fruit'(可能是'Pear'或'Apple')來調用一個函數,並且你給它一個至少需要一個'Pear'的函數。 – Niall 2014-10-16 11:58:11

+2

Niall是對的。 'useCallback'可能會給''thisIsagivenFunction''指向'C'對象的'A *'指針。你的函數必須接受**所有**'A'對象,而不是子集。 – MSalters 2014-10-16 12:05:50

14

讓我們假設你的類層次結構是有點大:

struct A { int a; }; 
struct B : A { int b; }; 
struct C : A { int c; }; 

,你擁有的功能像下面:

void takeA(A* ptr) 
{ 
    ptr->a = 1; 
} 

void takeB(B* ptr) 
{ 
    ptr->b = 2; 
} 

有了這樣的,我們可以說,takeA調用任何實例類派生自A(或A本身),並且該takeB可調用與任何實例的cl屁股B

takeA(new A); 
takeA(new B); 
takeA(new C); 

takeB(new B); 
// takeB(new A); // error! can't convert from A* to B* 
// takeB(new C); // error! can't convert from C* to B* 

現在,std::function是,它是一個包裝可贖回對象。它不那麼在意存儲函數對象的簽名,只要該對象是調用std::function包裝參數:

std::function<void(A*)> a; // can store anything that is callable with A* 
std::function<void(B*)> b; // can store anything that is callable with B* 

你所要做的,就是std::function<void(B*)>轉換爲std::function<void(A*)>。換句話說,您想要將包含B*的可調用對象存儲在包裝類中,其功能爲A*。是否存在A*B*的隱式轉換?不,那裏沒有。

也就是說,一個可以作爲很好的指針調用std::function<void(A*)>C類的一個實例:

std::function<void(A*)> a = &takeA; 
a(new C); // valid! C* is forwarded to takeA, takeA is callable with C* 

如果std::function<void(A*)>可以包裝調用對象的實例只B*服用,你將如何指望它與C*一起工作?:

std::function<void(B*)> b = &takeB; 
std::function<void(A*)> a = b; 
a(new C); // ooops, takeB tries to access ptr->b field, that C class doesn't have! 

幸運的是,上面的代碼沒有編譯。

然而,這種反其道而行之的方式是罰款:

std::function<void(A*)> a = &takeA; 
std::function<void(B*)> b = a; 
b(new B); // ok, interface is narrowed to B*, but takeA is still callable with B* 
2

它的工作原理,但在相反的方向:

struct A {}; 
struct B: A {}; 

struct X {}; 
struct Y: X {}; 

static X useCallback(std::function<X(B)> callback) { 
    return callback({}); 
} 

static Y cb(A) { 
    return {}; 
} 

int main() { 
    useCallback(cb); 
} 

回調的簽名聲明什麼將被傳遞給它,什麼是被收回。具體的回調可以採取較少的具體類型,如果不關心他們太多。同樣,它可以返回更具體的類型,額外的信息將被剝離。參考協變與逆變類型(簡化措辭中的輸入/輸出)。

+0

沒錯。請參閱std :: function中的逆變函數http://cpptruths.blogspot.com/2015/11/covariance-and-contravariance-in-c.html#function_contravariance – Sumant 2015-11-18 23:34:42

相關問題