Menci Lucio Ответов: 3

Избегайте определения всех методов в суперклассе, когда мне нужно создавать функторы


Привет,

Смотрите код:
class BaseData {
  public:
  BaseData() {}
  virtual int add() = 0;
  virtual int multiply() = 0;
  virtual int click() = 0;
  virtual int clack() = 0;
};

class Derived : public BaseData {
  int a, b;
  public:
  Derived(int a, int b) : BaseData() {
    this->a = a;
    this->b = b;
  }
  int add() {
    return a + b;
  }
  int multiply() {
    return a * b;
  }
  int click() {return 0;}
  int clack() {return 0;}
};

class Other : public BaseData {
  int a, b;
  public:
  Other(int a, int b) : BaseData() {
  	this->a = a;
    this->b = b;
  }
  int click() {
    return a + b;
  }
  int clack() {
    return a * b;
  }
  int add() {return 0;}
  int multiply() {return 0;}
};

class Operator {
  int (BaseData::* operation)();
  BaseData *obj;
  public:
  Operator(BaseData *object, int (BaseData::*op)()) : obj(object) {
    this->operation = op; 
  }
  int doOperation() {
    return (obj->*operation)();
  }
};


Derived *data1 = new Derived(33, 3);
Other *data2 = new Other(50, 2);
Operator *o1 = new Operator(data1, &BaseData::add);
Operator *o2 = new Operator(data1, &BaseData::multiply);
Operator *o3 = new Operator(data2, &BaseData::click);
Operator *o4 = new Operator(data2, &BaseData::clack);

void setup()
{
  Serial.begin(9600);
}

int main()
{
  Serial.write(o1->doOperation());
  Serial.write(o2->doOperation());
  Serial.write(o3->doOperation());
  Serial.write(o4->doOperation());
}


Я хотел бы избежать того, чтобы ссылочные методы были объявлены в базовом классе и определены во всех производных классах.

Что я уже пробовал:

Я попытался определить typedef в базовый класс, но безуспешно:
class BaseData {
  public:
  typedef int (BaseData::*fx)();
  BaseData() {}
};

class Derived : public BaseData {
  int a, b;
  int _add() {
    return a + b;
  }
  int _multiply() {
    return a * b;
  }
  public:
  Derived(int a, int b) : BaseData() {
  	this->a = a;
    this->b = b;
  }
  fx add = &Derived::_add;
  fx multiply = &Derived::_multiply;
}


Компилятор вернет эту ошибку
error: cannot convert 'int (Derived::*)()' to 'BaseData::fx {aka int (BaseData::*)()}' in initialization


Есть ли другой способ? Где я ошибаюсь?

0x01AA

Кто заставляет вас определять методы в базовом классе как чисто абстрактные?

Menci Lucio

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

Stefan_Lang

Ваш запрос не ясен: если вы не хотите определять функции в своем базовом классе, то зачем вам вообще нужен базовый класс?

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

P.S.: извините: я только что заметил, что ваш первый блок кода имеет полосу прокрутки - я пропустил код дальше вниз

3 Ответов

Рейтинг:
4

Stefan_Lang

Извините за поздний ответ, но я не мог решить, как взять ваш пример кода, поскольку представленная там функциональность не имеет смысла, и я не мог придумать хороший альтернативный пример, который имел бы больше смысла, не меняя слишком сильно подразумеваемые требования.

Во всяком случае, я скажу вам, что я думаю об этом:

1. В C++ нет абсолютно никакой необходимости для указателей на функции любого вида. Я программирую на C++ еще до появления одноступенчатых компиляторов в середине 80-х годов, и мне никогда не приходилось сталкиваться с требованиями, которые нельзя было бы решить с помощью разумного набора классов и полиморфизма. Чего бы вы на самом деле ни хотели достичь, вы можете заставить его работать с разумной иерархией классов.

Хотя я видел угловые случаи, когда такое решение не было практичным из-за необычных ограничений, в вашем вопросе я не вижу ничего подобного.

Что приводит нас к:
2. Вы не дали нам содержательной спецификации. Пример кода, похоже, не выполняет никакой значимой функции, и даже не зная, как вы реализовали решение, код, который его использует, кажется странным: все, что он делает, это бросает независимые данные в один и тот же горшок, а затем пытается поместить квадратные объекты в круглое отверстие, если вы понимаете, что я имею в виду. Либо вы должны дать нам пример кода, который отражает, как вы на самом деле собираетесь использовать решение, либо, что еще лучше, дать нам исходную спецификацию, если она у вас есть.

3. я могу сделать некоторые обоснованные предположения о том, что вам нужно, но не код, который превращает ваш странный пример во что-то, что имеет хоть какой-то смысл. начнем с некоторых наблюдений:

а) ваши классы, по-видимому, пытаются реализовать что-то, что известно как шаблон проектирования команд. Видеть Командный шаблон - Википедия[^]

b) in your classes, you've defined a restricted set of functions, as well as the arguments that these functions are supposed to operate on. It's not a good idea to specify more than one function per class though: if for some reason you need to somehow change the argument number or types for only one of these functions, how can you resolve that? You've created one-to-one relationships between the functions and the data. If you have multiple functions operating on the same data, then either you need to duplicate the data and put each function and associated data into a separate object, or you need to put the data into a separate object, apart from the functions.

в) у вас есть несколько уровней абстракций, и на последнем уровне вы вызываете только одну функцию: doOperate(). Почему бы не определить ваши классы таким образом, чтобы они реализовывали ровно по одной функции каждый?

4. вышеизложенное наводит меня на мысль, что вам действительно нужно реализовать шаблон команды. Я мог бы предложить код, соответствующий вашему примеру, но сайт, на который я ссылался выше, уже хорошо объясняет, что вам нужно, и он также предлагает некоторые примеры кода (пример C++ находится в нижней части страницы). Попробовать это.


Menci Lucio

Да, конечно, вы правы. После многих головных болей, вызванных моим неправильным выбором, я строю свой проект из 0, используя некоторые командные классы.
Я новичок в c++, я пишу на C#, и мне было интересно, есть ли способ сделать что-то вроде событий. Но C++ - это не c#. Возможно, события C# были сделаны по шаблону команд...

Спасибо

Рейтинг:
2

KarstenK

Может быть, указатели функций-это то, что вы ищете. Читать это Typedef для указателей функций.

Но я предупреждаю вас: это твердое ядро С, и вы можете получить некоторые головные боли, пока не овладеете им. ;-)


Menci Lucio

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

fx add = &Derived::_add;
возвращается сообщение об ошибке
error: cannot convert 'int (Derived::*)()' to 'BaseData::fx {aka int (BaseData::*)()}' in initialization

Это было результатом моей головной боли :-)

Menci Lucio

Привет, спасибо, но если я не хочу использовать виртуальные методы и переопределять их, есть несколько причин. У меня есть много подклассов класса baseclass в моей проектной продукции, например, существуют светодиодные лампы (виртуальные методы, должен быть switchOn, выключения, switchState, методы starttimer, мгновение, blinkFast), serialQueue (sendLocalMessage, sendGlobal, sendARequest), дисплей (showState, выключения, showError), и многие другие. Я попытался определить все виртуальные методы в базовом классе, но все они должны быть определены в производные (см. код, который я опубликовал).

Последняя, но не менее важная причина заключается в том, что я хотел бы знать, есть ли способ сделать это, для личного знания и для гордости :-)

Stefan_Lang

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

Если по какой-то непонятной причине вам абсолютно необходим глобальный базовый класс для всего, вам действительно нужно ввести промежуточные слои в свою иерархию классов: определить промежуточный класс для светильников, другой для тостеров и еще один для стиральных машин. А затем используйте эти промежуточные базовые классы в своей программе, а не конечный (и ИМХО совершенно бессмысленный) базовый класс.

Rick York

Я согласен со Стефаном. Еще одна вещь - я думаю, что это нормально иметь базовый класс, но вам не нужно делать все чисто виртуальным (=0). Вы можете указать значения по умолчанию, которые являются просто пустыми скобками, по крайней мере "{}". Это означает, что вам нужно только реализовать то, что необходимо. Я также согласен с ним относительно промежуточных слоев, таких как свет. Он все еще может быть производным от базового класса, но у него будут реальные реализации методов включения и выключения. Вы даже можете сделать дополнительную специализацию для огней, которые могут быть затемнены (LightDim), предполагая, что не все из них могут быть, что верно для некоторых видов.

Рейтинг:
0

Rick York

Это C++, так что вам не придется возиться с указателями функций. Вы можете использовать виртуальные методы и просто переопределить те, которые вы хотите настроить. Если вы используете функции в качестве обратных вызовов windows или потоковых процедур, они должны быть статическими членами, поскольку с ними не используются указатели "это".

Я не вижу убедительной причины использовать указатели функций в коде, который вы показали, поэтому я бы их избегал.

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

typedef BaseObject* ( * ObjCreateFunc )();

typedef struct
{
    UINT          controlId;
    ObjCreateFunc createFunc;
} IdMap;

IdMap Map[ MAX_OBJECTS ] = { 0 };

// it was invoked like this:

BaseObject * pobj = (* Map[ index ].createFunc )();
Я недавно переписал этот раздел, используя Заводской шаблон, и теперь он намного чище. Он больше не использует указатели функций для создания объектов. Он использует класс фабрики виртуальных объектов, который переопределяется для создания каждого из отдельных объектов. Существует восемнадцать различных объектов, которые могут быть созданы, в зависимости от того, какая кнопка нажата, и существует аналогичный массив структур, который связывает идентификатор элемента управления с фабрикой объектов.