2014-10-16 26 views
12

Đầu tiên, tôi định nghĩa hai lớp, kế thừa từ một lớp khác.Chuyển đổi std :: function <void (Derived *)> thành std :: function <void(Base*)>

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

Sau đó, tôi tuyên bố một chức năng mà sử dụng một std::function<void(A*)>:

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

Cuối cùng, tôi nhận được một std::function của một khác nhau (nhưng về mặt lý thuyết tương thích) gõ từ một nơi nào khác mà tôi muốn sử dụng trong chức năng gọi lại của tôi:

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

useCallback(thisIsAGivenFunction); 

Trình biên dịch của tôi (clang ++) từ chối vì loại thisIsAGivenFunction không khớp với loại mong muốn. Nhưng với B được kế thừa từ A, sẽ có ý nghĩa đối với thisIsAGivenFunction để được chấp nhận.

Có phải không? Nếu không, tại sao? Và nếu có, thì tôi đang làm gì sai?

+1

có thể trùng lặp của [Đa hình mẫu C++] (http://stackoverflow.com/questions/2203388/c-templates-polymorphism) – Samuel

+3

không trùng lặp, 'std :: function' hỗ trợ * chuyển đổi * đó. nhưng theo cách khác, bạn có thể chuyển đổi 'std :: function ' thành 'std :: function ', bởi vì hàm hoạt động trên 'A *' có thể làm tương tự khi nó nhận một thể hiện của 'B *', nhưng không phải cách khác –

+0

Nguồn của vấn đề của bạn đang sử dụng 'std :: function' như một cuộc gọi lại thay vì chấp nhận một' Functor' làm đối số mẫu. Trừ khi bạn có một lý do chính đáng để làm điều này, đừng làm điều đó. – pmr

Trả lời

2

Bạn không thể vượt qua &Foo(Apple) khi ai đó có thể chuyển cho bạn ngẫu nhiên Fruit bao gồm Pear.

+0

Tôi không hiểu. Trong trường hợp của tôi, hàm 'useCallback' của tôi mong đợi một' Fruit' ngẫu nhiên, và khi tôi chuyển nó thành một 'Pear' trình biên dịch phàn nàn. – Ecco

+1

@Ecco. Tôi nghĩ rằng điều này là 'useCallback' của bạn hy vọng sẽ gọi một hàm với một' Fruit' (có thể là 'Pear' hoặc' Apple') và bạn đang cho nó một hàm mà hy vọng ít nhất là một 'Pear'. – Niall

+2

Niall đúng. 'useCallback' có thể cung cấp cho' thisIsAGivenFunction' một con trỏ 'A *' của đối tượng 'C'. Hàm của bạn phải chấp nhận ** tất cả ** đối tượng 'A', không phải là tập hợp con. – MSalters

14

Giả sử rằng hệ thống cấp bậc lớp học của bạn là lớn hơn một chút:

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

và bạn có chức năng như dưới đây:

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

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

Có đó, chúng tôi có thể nói rằng takeAcallable với bất kỳ ví dụ của lớp bắt nguồn từ A (hoặc A chính nó) và rằng takeBcó thể gọi với bất kỳ thể hiện nào của cl ass 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* 

Bây giờ, std::function là gì, nó là một wrapper cho callable đối tượng. Nó không quan tâm nhiều về chữ ký của lưu trữ đối tượng hàm càng lâu càng rằng đối tượng là callable với các thông số của std::function wrapper của nó:

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* 

gì bạn đang cố gắng để làm, là để chuyển đổi std::function<void(B*)> để std::function<void(A*)>. Nói cách khác, bạn muốn lưu trữ đối tượng có thể gọi lấy B* trong lớp trình bao bọc cho các hàm lấy A*. Có chuyển đổi tiềm ẩn của A* đến B* không? Không có.

Đó là, một cũng có thể gọi std::function<void(A*)> với một con trỏ đến một thể hiện của lớp C:

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

Nếu std::function<void(A*)> thể quấn một thể hiện của đối tượng callable chỉ B* uống, làm thế nào bạn mong chờ nó để làm việc với 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! 

May mắn thay, mã trên không biên dịch.

Tuy nhiên, làm điều này theo cách ngược lại là tốt:

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

Nó hoạt động nhưng theo hướng ngược lại:

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); 
} 

Chữ ký của callback tuyên bố những gì sẽ được truyền cho nó và những gì là được trở lại. Cụ thể gọi lại có thể mất ít loại cụ thể nếu không quan tâm quá nhiều về chúng. Tương tự, nó có thể trả về loại cụ thể hơn, thông tin bổ sung sẽ bị tước bỏ. Tham khảo các loại biến đổi so với các biến thể (đầu vào/đầu ra trong từ ngữ đơn giản).

+0

Phải. Xem contravariance trong std :: function http://cpptruths.blogspot.com/2015/11/covariance-and-contravariance-in-c.html#function_contravariance – Sumant

Các vấn đề liên quan