Виртуальные методы или указатели функций

Сообщение об ошибке, похоже, указывает на то, что это strlen, прочитанное за буфером malloc ed, выделенным strdup. На 32-битной платформе оптимальная реализация strlen может считывать по 4 байта за раз в 32-разрядный регистр и делать несколько бит-скрипов, чтобы увидеть, есть ли там нулевой байт. Если в конце строки осталось меньше 4 байтов, но 4 байта по-прежнему читаются для выполнения проверки нулевого байта, тогда я мог видеть, что эта ошибка печатается. В этом случае предположительно исполнитель strlen будет знать, «безопасно» ли это сделать на конкретной платформе, и в этом случае ошибка valgrind является ложным положительным.

30
задан Thomas Bonini 23 December 2009 в 20:31
поделиться

7 ответов

Подход 1 (Виртуальная функция)

  • "+" Правильный способ сделать это на C++
  • "-" Должен быть создан новый класс на один обратный вызов
  • "-" по производительности - дополнительное разыменование через VF-таблицу по сравнению с указателем функции. Две косвенные ссылки по сравнению с решением Functor.

Approach 2 (Class with Function Pointer)

  • "+" Можно обернуть функцию в стиле C для C++ Callback Class
  • "+" Callback function может быть изменена после создания объекта обратного вызова
  • "-" Требуется непрямой вызов. Может быть медленнее, чем метод functor для обратных вызовов, которые могут быть статически вычислены во время компиляции.

Approach 3 (Class calling T functor)

  • "+" Возможно самый быстрый способ сделать это. Никаких косвенных накладных расходов на вызов и может быть полностью вложен.
  • "-" Требуется определить дополнительный класс Functor.
  • "-" Требуется, чтобы обратный вызов был объявлен статически во время компиляции.

FWIW, Указатели функций не являются тем же самым, что и Functors. Functors (на Си++) - это классы, которые используются для обеспечения вызова функции, которая обычно является operator().

Вот пример functor, а также шаблонная функция, которая использует аргумент functor:

class TFunctor
{
public:
    void operator()(const char *charstring)
    {
        printf(charstring);
    }
};

template<class T> void CallFunctor(T& functor_arg,const char *charstring)
{
    functor_arg(charstring);
};

int main()
{
    TFunctor foo;
    CallFunctor(foo,"hello world\n");
}

С точки зрения производительности, Виртуальные функции и Указатели Функций приводят к косвенному вызову функции (т.е. через регистр), хотя виртуальные функции требуют дополнительной загрузки указателя VFTABLE перед загрузкой указателя на функцию. Использование Functors (с невиртуальным вызовом) в качестве обратного вызова является наиболее эффективным методом использования параметра для шаблонирования функций, поскольку они могут быть индуктированы и даже если не индуктированы, не генерируют непрямого вызова.

.
13
ответ дан 27 November 2019 в 21:13
поделиться

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

Когда я пишу C++, единственной плоской функцией, которую я пишу, является int main(). Все остальное является объектом класса. Из двух вариантов я бы выбрал определение класса и переопределил вашу виртуальную функцию, но если все, что вы хотите - это уведомить какой-нибудь код о том, что в вашем классе произошло какое-то действие, то ни один из этих вариантов не будет лучшим решением.

Я не знаю вашей точной ситуации, но вы можете захотеть просмотреть шаблоны дизайна

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

0
ответ дан 27 November 2019 в 21:13
поделиться

Подход 1

  • Легче читать и понимать
  • Меньшая вероятность ошибок (iFunc не может быть NULL, вы не используете пустоту *iParam, etc
  • C++ программисты скажут, что это "правильный" способ сделать это в C++

Approach 2

  • Slightly less typing to do
  • VERY немного быстрее (вызов виртуального метода имеет некоторые накладные расходы, обычно те же самые, что и две простые арифметические операции. . Поэтому, скорее всего, это не будет иметь значения)
  • Именно так вы бы сделали это в C

Approach 3

Вероятно, лучший способ сделать это, когда это возможно. Он будет иметь наилучшую производительность, он будет безопасен при вводе текста, и его легко понять (это метод, используемый в STL)

.
6
ответ дан 27 November 2019 в 21:13
поделиться

Из вашего примера не ясно, создаете вы класс утилиты или нет. Вы Callback класс предназначен для реализации закрытия или более существенного объекта, который Вы просто не дополнили? Первая форма:

  • Легче читать и понимать,
  • Гораздо проще расширить: попробуйте добавить методы паузы, возобновить и стоп .
  • Лучше обрабатывать инкапсуляцию (предполагая, что в классе определено doGo).
  • Вероятно, лучше абстрагируется, поэтому его легче поддерживать.

Вторая форма:

  • может использоваться с различными методами для doGo, поэтому она больше, чем просто полиморфная.
  • Могло бы позволить (с помощью дополнительных методов) изменять метод doGo во время выполнения, позволяя экземплярам объекта мутировать их функциональность после создания.

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

.
3
ответ дан 27 November 2019 в 21:13
поделиться

Одним из главных преимуществ первого метода является большая безопасность типа. Второй метод использует пустоту * для iParam, поэтому компилятор не сможет диагностировать проблемы с типами.

Незначительным преимуществом второго метода является то, что было бы меньше работы по интеграции с Си. Но если в основе кода только Си++, то это преимущество является спорным.

.
1
ответ дан 27 November 2019 в 21:13
поделиться

Например, давайте посмотрим на интерфейс для добавления read функциональности к классу:

struct Read_Via_Inheritance
{
   virtual void  read_members(void) = 0;
};

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

struct Read_Inherited_From_Cin
  : public Read_Via_Inheritance
{
  void read_members(void)
  {
    cin >> member;
  }
};

Если я хочу читать из файла, базы данных или USB, это требует еще 3 отдельных класса. Комбинации начинают быть очень уродливыми с множеством объектов и множеством источников.

Если я использую functor, который случайно напоминает шаблон проектирования Visitor:

struct Reader_Visitor_Interface
{
  virtual void read(unsigned int& member) = 0;
  virtual void read(std::string& member) = 0;
};

struct Read_Client
{
   void read_members(Reader_Interface & reader)
   {
     reader.read(x);
     reader.read(text);
     return;
   }
   unsigned int x;
   std::string& text;
};

С вышеуказанным фундаментом, объекты могут читаться из разных источников, просто снабжая разных читателей методом read_member:

struct Read_From_Cin
  : Reader_Visitor_Interface
{
  void read(unsigned int& value)
  {
     cin>>value;
  }
  void read(std::string& value)
  {
     getline(cin, value);
  }
};

Мне не нужно менять код объекта (это хорошо, потому что он уже работает). Я также могу применить читатель к другим объектам.

Обычно я использую наследование, когда выполняю общее программирование . Например, если у меня есть класс Field, то я могу создать Field_Boolean, Field_Text и Field_Integer. In может поместить указатели на свои экземпляры в вектор и назвать его записью. Запись может выполнять общие операции с полями, и не заботится или не знает, что вид поля обрабатывается.

0
ответ дан 27 November 2019 в 21:13
поделиться
  1. Изменение на чисто виртуальный, во-первых. Затем вставьте его в строку. Это должно вообще отменить любой вызов метода, если только встраивание не даст сбой (и не даст сбой, если вы его заставите).
  2. Может также использовать C, потому что это единственная реально полезная главная особенность C++ по сравнению с C. Вы всегда будете вызывать метод и его нельзя будет встраивать, так что он будет менее эффективным.
0
ответ дан 27 November 2019 в 21:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: