2013-04-29 4 views
10

Я написал код и перепугался, что он не будет работать - так что я написал прототип:Boost :: Перегрузка и перегрузка виртуальных функций: зачем они работают?

#include <boost/function.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 

class base { 
private: 
    boost::function<void (int)> action; 
protected: 
    virtual void onDataBaseReady(int i) { std::cout << i << std::endl; } 
public: 
    void call() { 
     action(10); 
    } 

    base() { 
     action = boost::bind(&base::onDataBaseReady, this, _1); 
    } 
}; 

class child : public base { 
protected: 
    virtual void onDataBaseReady(int i) { std::cout << i+10 << std::endl; } 
}; 

int main() 
{ 
    static child c; 
    c.call(); 
    std::cin.get(); 
    return 0; 
} 

, который компилирует и работает. (выходы 20). Но почему? Также я тестировал в VS2010 и задался вопросом, будет ли он работать на разных платформах (например, скомпилирован под GCC)?

В основном action = boost::bind(&base::onDataBaseReady, this, _1); меня пугает - мы говорим &base:: ...

+1

* мы говорим, и база :: ... * и мы указываем на функцию с полиморфным поведением. Меня это испугало бы, если бы он не назвал окончательного перехватчика! –

ответ

13

Указатель на метод virtual делает virtual функцию поиска в при вызове.

#include <iostream> 
#include <memory> 

struct base { 
    virtual void foo() { std::cout << "base\n"; } 
    virtual ~base() {} 
}; 

struct derived:base { 
    void foo() override final { std::cout << "derived\n"; } 
}; 

int main() { 
    void (base::*mem_ptr)() = &base::foo; 
    std::unique_ptr<base> d(new derived()); 
    base* b = d.get(); 
    (b->*mem_ptr)(); 
} 

так что это «просто работает». Указатель функции-члена (this->*&base::foo)() не совпадает с полным вызовом функции this->base::foo(). Первый способ сохранить foo часть вызова this->foo(), второй способ пропустить метод поиска virtual и позвонить по телефону base::foo.

+0

@idmean Извините, у меня есть религиозные возражения против владения сырыми указателями. – Yakk

1

Главным образом действие = boost :: bind (& base :: onDataBaseReady, this, _1); меня пугает - мы говорим & база :: ...

Это было на самом деле гораздо более страшно, если она выполняется статический отправки, а не динамической диспетчеризации. Рассмотрим простой пример:

struct base { 
    virtual void foo() { /* maintain some invariants */ } 
}; 
struct derived : base { 
    virtual void foo() { /* maintain different invariants */ } 
}; 

А потом считают, что вы связываете функцию у родителя и вызвать его на объект производного. Разработчик derived знает, какие инварианты относятся к производному типу, который может быть тем же самым, подмножеством или полностью отличным от инвариантов базового типа.

void apply(base & b) { 
    std::bind(&base::foo, &b)(); 
} 

Если отправка была решена во время связывания, и функтор был применен к производному типу (из которых вы можете не знать точный тип!), То инварианты производного типа может быть нарушена. В контексте функции apply невозможно понять, что такое объект на самом деле, или что такое инварианты этого типа, поэтому то, что вы, вероятно, хотите сделать, это позволить динамической диспетчеризации делать свою магию.

[То есть с точки дизайна высокого уровня зрения, даже не вдаваясь в детали, что вы не можете использовать указатель члена для выполнения статической отправки ...]