Подтвердить что ты не робот

Почему указатели на объекты не считаются объектно-ориентированными?

В спецификациях языка С# он явно указывает:

Делегаты похожи на концепцию указателей функций, найденных в некоторых другие языки, но в отличие от функции указатели, делегаты объектно-ориентированный и безопасный тип.

Я понимаю, что делегаты должны быть немного более гибкими, чем указатели, потому что .NET перемещает память. Это единственное различие, о котором я знаю, но я не уверен, как это превратит делегата в концепцию OO...?

Что делает указатель функции не объектно-ориентированным? Являются ли указатели и указатели функций эквивалентными?

4b9b3361

Ответ 1

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

Указатели функций не содержат данных, они не инкапсулируют детали реализации, они не отправляют и не получают сообщения, они не являются модульными, они обычно не используются полиморфным образом (хотя, я полагаю, они теоретически могут быть ковариантными и контравариантными по их возвратным и формальным типам параметров, поскольку делегаты теперь находятся на С# 4), и они не участвуют в иерархии наследования. Они не описывают себя; вы не можете запросить указатель функции для своего типа, потому что у него его нет.

В отличие от этого делегаты захватывают данные - они держатся за приемник. Они поддерживают обмен сообщениями в том смысле, что вы можете "сообщать" делегату, вызывая методы ToString или GetType или Invoke или BeginInvoke, чтобы сказать ему что-то сделать, и он "отправляет" сообщение с результатом. Типы делегатов могут быть ограничены определенными доменами доступности, если вы решите это сделать. Они являются самоописательными объектами, которые имеют метаданные и во время выполнения знают свой собственный тип. Их можно комбинировать с другими делегатами. Их можно использовать полиморфно, как System.MulticastDelegate или System.Delegate, типы, из которых они наследуют. И они могут быть использованы полиморфно в том смысле, что в С# 4 типы делегатов могут быть ковариантными и контравариантными по своим типам возврата и параметров.

Ответ 2

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

Ответ 3

Указатели функций - это только адреса памяти.

Делегаты - это объекты, которые имеют методы и свойства:
-BeginInvoke
-DynamicInvoke
-Invoke
-метод
-target
и др.

Ответ 4

Я объясню с примерами С++, потому что это язык, на котором эта проблема присутствует (и решается другим способом).

Просто указатель на функцию просто содержит адрес функции, ничего больше.

Рассмотрим функцию

void f(int x) { return; }

Теперь простой указатель функции объявлен и назначен следующим образом:

void (*fptr)(int) = &f;

И вы можете использовать его просто:

foo(5); // calls f(5)

Однако на объектно-ориентированном языке мы обычно имеем дело с функциями-членами, а не с бесплатными функциями. И тут все становится неприятно. Рассмотрим следующий класс:

class C { void g(int x) { return; } };

Объявление указателя на функцию C:: g выполняется следующим образом:

void (*C::gptr)(int) = &C::g;

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

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

C c;
(c.*gptr)(5);  // calls c.g(5);

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

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

BTW решение С++ в С++ 0x принимается из Boost. Он называется std::function и std::bind и работает следующим образом:

std::function<void (C*, int)> d = std::bind(&c::g, &c);
d(5); // calls c.g(5);

Ответ 5

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

Ответ 6

Предположим, что мы хотим создать метод anyprintf общего назначения, который может вести себя как fprintf, sprintf, cprintf [console printf с поддержкой цвета]. Один из подходов заключался бы в том, чтобы он принял функцию, которая принимает void* и char вместе с void* и va_list; он должен затем для каждого символа вывода вызывать переданную функцию, передавая ей поставленный указатель и выводимый символ.

Для такой функции можно реализовать vsprintf и fprintf [игнорируя их возвращаемые значения для упрощенного) с помощью:

void fprint_function(void* data, char ch) { fputc( (FILE*)data, ch); }
void sprint_function(void* data, char ch) { char**p = (char**)data; *((*p)++) = ch; }
void fprint_function(void* data, char ch) { cputchar( ch); }
void vfprintf(FILE *f, va_list vp, const char *fmt, va_list vp)
{
  vsanyprintf(fprint_function, (void*)f, st, vp);
}
void vsprintf(char *st, va_list vp, const char *fmt, va_list vp)
{
  vsanyprintf(fprint_function, (void*)f, st, vp);
}
void vcprintf(va_list vp, const char *fmt, va_list vp)
{
  vsanyprintf(cprint_function, (void*)0, st, vp);
}

Эффективно, комбинация указателя функции и void* ведет себя как метод. К сожалению, компилятор не может гарантировать, что данные, которые передаются в void*, будут иметь форму, ожидаемую поставляемой функцией. С++ и другие объектно-ориентированные языки добавляют во время проверки целостности такого типа во время компиляции.