C ++: привязка к базовому классу - PullRequest
3 голосов
/ 16 апреля 2010

EDIT:

В следующем коде 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;
}

Ответы [ 3 ]

3 голосов
/ 16 апреля 2010

То, что вы делаете со своим оператором «boost :: bind», это вызываете производный :: тест и передаете «b» как указатель «this». Важно помнить, что указатель «this» для производного :: теста должен быть указателем на «производный» объект - но это не так. Это работает в вашей конкретной ситуации, поскольку у вас нет vtable и структура памяти идентична - но как только это изменится, ваша программа, скорее всего, сломается.

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

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

1 голос
/ 16 апреля 2010

На обновленный вопрос:

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

1 голос
/ 16 апреля 2010

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

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

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

Когда вы объявляете base::test virtual, вы получаете правильное поведение: ваш самый производный объект в иерархии - 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))();
}
...