2010-04-16 7 views
3

РЕДАКТИРОВАТЬ:С ++: Привязка к базовому классу

В следующем коде container::push принимает объект типа T, производный от base в качестве аргумента, и сохраняет в векторе указатель на метод bool T::test().

container::call вызывает каждый из сохраненных методов в контексте к объекту члена p, который имеет тип base, не T. Он работает до тех пор, пока вызываемый метод не ссылается ни на какой член вне base, и если test() не объявлен virtual.

Я знаю, что это уродливо и может быть даже не совсем правильным.

Как я могу сделать то же самое лучше?

#include <iostream> 
#include <tr1/functional> 
#include <vector> 

class base { 
    public: 
    base(int v) : x(v) 
    {} 
    bool test() const { // this is NOT called 
     return false; 
    } 

    protected: 
    int x; 
}; 

class derived : public base { 
    public: 
    bool test() const { // this is called instead 
     return (x == 42); 
    } 
}; 

class container { 
    public: 
    container() : p(42) 
    {} 
    template<typename T> 
    void push(const T&) { 
     vec.push_back((bool (base::*)() const) &T::test); 
    } 
    void call() { 
     std::vector<bool (base::*)() const>::iterator i; 
     for(i = vec.begin(); i != vec.end(); ++i) { 
     if((p .* (*i))()) { 
      std::cout << "ok\n"; 
     } 
     } 
    } 

    private: 
    std::vector<bool (base::*)() const> vec; 
    base p; 
}; 

int main(int argc, char* argv[]) { 
    container c; 
    c.push(derived()); 
    c.call(); 
    return 0; 
} 
+0

В чем вопрос? – outis

+1

Изменение вопроса после его ответа приводит к целому ряду замешательств (внезапно есть ответы, которые не связаны или игнорируют часть вопроса только потому, что вопрос изменился). Пожалуйста, не надо. Добавьте другой вопрос, если вам это нужно. И, кстати, обычно это помогает, если вы добавляете то, что хотите, и почему оно вам нужно, а не код, который у вас есть. –

+0

Вы правы, я голосовал, чтобы закрыть вопрос. –

ответ

1

К обновленному вопрос:

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

3

Что вы делаете с вашим утверждением «повышение :: привязки» является вызовом производного :: теста и пройти «б» как «это» указатель. Важно помнить, что «этот» указатель на метод output :: test должен быть указателем на «производный» объект - это не так для вас. Он работает в вашей конкретной ситуации, так как у вас нет vtable, а макет памяти идентичен, но как только это изменится, ваша программа, скорее всего, сломается.

И кроме того, это просто неправильно - уродливый, нечитаемый, подверженный ошибкам код. Что вы на самом деле пытаетесь сделать?

[Изменить] Новый ответ на отредактированный вопрос: вы должны использовать boost :: bind для создания функционального закрытия, которое обертывает объект & функцией-членом в одном объекте и сохраняет этот объект в вашей коллекции. Затем, когда вы вызываете его, он всегда надежный. Если вы не можете использовать boost в своем приложении ... ну, вы, можете сделать что-то вроде boost :: связать себя (просто посмотреть, как это делается в boost), но, скорее всего, вы его получите неправильно и имеют ошибки.

+0

Я переписал вопрос, ясно ли это сейчас? –

1

Что вы делаете неправильно, и в простом примере оно будет работать, но может просто поднять ад (одну из возможностей для неопределенного поведения) в других случаях.

base::test С и derived::test не виртуальные, они представляют собой два различных метода членов, так и для simplicitly я буду называть их base::foo и derived::bar. В коде связывания вы вынуждаете компилятор адаптировать указатель к bar, который определен в derived, как если бы он был определен в base и затем вызвал его. То есть вы вызываете метод derived на объект или тип base !!! что является неопределенным поведением.

Причина, по которой он не умирает в том, что this указатели в base и derived совпадают, и что вы только доступ к данным, присутствующие в base классе. Но это неверно.

Когда вы объявляете base::test виртуальных, вы получите правильное поведение: ваш самые производный объект в иерархии base, компилятор будет использовать виртуальный механизм доставки и узнать, что base где конечные подмены для test найдены и выполняются ,

Когда вы объявляете только derived::test как виртуальный (а не base), компилятор попытается использовать несуществующий механизм виртуальной диспетчеризации (обычно указатель vtable) в переданном объекте и убивает приложение.

В любом случае, все, кроме виртуальных base::test используются неверно. В зависимости от того, что ваши фактических требований, наиболее вероятно, правильный способ сделать это будет:

class base { 
public: 
    virtual bool test() const; 
}; 
class derived : public base { 
public: 
    virtual bool test() const; // <--- virtual is optional here, but informative 
}; 
int main() 
{ 
    derived d; // <--- the actual final type 
    base & b = d; // <--- optional 
    if (std::tr1::bind(&base::test, std::tr1::ref(b))()) { 
     // ... 
    } 
} 

Обратите внимание, что нет литого (слепки, как правило, намек на что-то странное, потенциально опасном скрываются там), что объект имеет конкретный тип, в котором вы хотите вызвать метод, и что виртуальный механизм отправки гарантирует, что даже если привязка равна base::test, так как этот метод является виртуальным, окончательный перехват будет выполнен.

Это другой пример, более вероятно делать смешные вещи (я не пробовал):

struct base { 
    void foo() {} 
}; 
struct derived : base { 
    void foo() { 
     for (int i = 0; i < 1000; ++i) { 
     std::cout << data[i]; 
     } 
    } 
    int data[1000]; 
}; 
int main() { 
    base b; 
    std::tr1::bind((void (base::*)()) &derived::foo, std::tr1::ref(b))(); 
} 
+0

Спасибо за объяснение, но не должно std :: tr1 :: ref (b) быть std :: tr1 :: ref (d) в вашем примере? –

+1

Ухм ... проблема в том, что я забыл инициализировать рефери. Код даже не компилируется. В любом случае с исправленным кодом вы можете использовать либо 'b', либо' d', поскольку оба они относятся к одному и тому же объекту, и оба являются «базовыми» –

Смежные вопросы