LINUX.ORG.RU

Почему нельзя получить указатель на не-статический метод класса даже внутри класса?

 , , ,


0

4

Очень простой вопрос, который нигде не объясняется.

Почему в C++ нельзя получить указатель на нестатический метод класса даже внутри класса?

Из объяснений я нашел только:

Что касается нестатических методов, то их адреса — это не указатели и их нельзя присвоить указателю на функцию.

Источник

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

Источник

Во второй цитате даже сказали что «без объекта». Но я проверил, даже с текущим объектом (используя this, хотя он и так должен использоваться по-умолчанию) получение указателя не работает.

★★★★★

Почему в C++ нельзя получить указатель на нестатический метод класса даже внутри класса?

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

Вопрос в том — зачем ты этого хочешь?

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

т.е. конкретный адрес функции зависит от конкретного объекта.

Но в рамках самого объекта в чем проблема получить указатель на метод?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Gvidon

Наколько я понял, то, о чем ты говоришь, находится в разделе «Pointers to member functions». Но моих знаний англицкого недостаточно, чтобы понять о чем там идет речь (я бы и нарусском не понял).

Объясни, что ты хотел сказать этой ссылкой. Что значит «не совсем тот указатель»?

Xintrea ★★★★★
() автор топика

Потому что код методов (который в сегменте .text) не копируется на каждый инстанс класса.

redixin ★★★★
()
Ответ на: комментарий от Legioner

Вопрос в том — зачем ты этого хочешь?

Чтобы вместо этого уродства:

void EditorConfig::update_version_process(void)
{
int fromVersion=get_config_version();
// Последняя версия на данный момент - 11
if(fromVersion<=1)
update_version(1, 2, get_parameter_table_1(), get_parameter_table_2());
if(fromVersion<=2)
update_version(2, 3, get_parameter_table_2(), get_parameter_table_3());
if(fromVersion<=3)
update_version(3, 4, get_parameter_table_3(), get_parameter_table_4());
if(fromVersion<=4)
update_version(4, 5, get_parameter_table_4(), get_parameter_table_5());
if(fromVersion<=5)
update_version(5, 6, get_parameter_table_5(), get_parameter_table_6());
if(fromVersion<=6)
update_version(6, 7, get_parameter_table_6(), get_parameter_table_7());
if(fromVersion<=7)
update_version(7, 8, get_parameter_table_7(), get_parameter_table_8());
if(fromVersion<=8)
update_version(8, 9, get_parameter_table_8(), get_parameter_table_9());
if(fromVersion<=9)
update_version(9, 10, get_parameter_table_9(), get_parameter_table_10());
if(fromVersion<=10)
update_version(10, 11, get_parameter_table_10(), get_parameter_table_11());
}


Можно было написать так:

void EditorConfig::update_version_process(void)
{
 int fromVersion=get_config_version();

 QList<QStringList (*)()> parameterFunctions;

 parameterFunctions << NULL; // Исторически счет версий идет с 1, NULL чтоб не путаться
 parameterFunctions << get_parameter_table_1;
 parameterFunctions << get_parameter_table_2;
 parameterFunctions << get_parameter_table_3;
 parameterFunctions << get_parameter_table_4;
 parameterFunctions << get_parameter_table_5;
 parameterFunctions << get_parameter_table_6;
 parameterFunctions << get_parameter_table_7;
 parameterFunctions << get_parameter_table_8;
 parameterFunctions << get_parameter_table_9;
 parameterFunctions << get_parameter_table_10;
 parameterFunctions << get_parameter_table_11;

 for(int i=1; i<parameterFunctions.count()-1; ++i)
   if(fromVersion<=i)
     update_version(i, i+1, (parameterFunctions.at(i))(), (parameterFunctions.at(i+1))() );
}


Приходится чтобы методы get_parameter_table_X были статическими, но это не всегда возможно.

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 2)
Ответ на: комментарий от redixin

Потому что код методов (который в сегменте .text) не копируется на каждый инстанс класса.

Ну и что, все равно метод при работе получает скрытый первый параметр this объекта, с которым идет работа, чтобы были доступны свойства именно этого объекта.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Тебе нужны std::function + std::bind или std::function + лямбды. В любом случае, просто указателями в общем случае не обойдёшься

Gvidon ★★★★
()
Ответ на: комментарий от Xintrea

Что значит «не совсем тот указатель»?

Указатели на члены (как на члены-данные, так и на члены-функции) — это отдельные типы данных, не совместимые с обычными указателями

Gvidon ★★★★
()

Если ОЧЕНЬ хочется, то «можно», но не нужно, т.к. есть std::function.

invy ★★★★★
()
Ответ на: комментарий от Xintrea

Ну и что? Да, так оно работает, но причем тут «указатель на метод инстанса»? Код метода ведь в одном экземпляре существует. Как можно получить указатель на то чего нет?

В этом плане сишечка проще, там есть указатель на метод, и указатель на инстанс. Берешь и вызываешь. Или например посмотри на классы в питоне, там забавно.

redixin ★★★★
()
Ответ на: комментарий от redixin

Код метода ведь в одном экземпляре существует. Как можно получить указатель на то чего нет?

Эмм, не понял мысль. Так существует или нет?

Gvidon ★★★★
()
Ответ на: комментарий от Gvidon

Инстансов в памяти машины много, но код метода есть только в одном экземпляре. Как ты представляешь себе получение указателя на метод инстанса номер 42? Ну допустим можно получить указатель, но он один для всех инстансов. Потому и можно только для статик методов.

redixin ★★★★
()
Ответ на: комментарий от redixin

Ну вообще-то под капотом все методы получают указатель на инстанс в виде первого аргумента (т.е. прототип всегда A::f(A* this, .... ));

invy ★★★★★
()
Ответ на: комментарий от Gvidon

А ты же и хочешь получить указатель на метод класса без привязки к инстансу. Но в этом нет смысла, т.к. нет способа вызывать этот метод.

redixin ★★★★
()
Ответ на: комментарий от Xintrea

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

vvviperrr ★★★★★
()
Ответ на: комментарий от Xintrea

Считай, что ты оптимизировал цикл, развернув его.

anonymous
()

Может я чего недопонимаю, но как-же

void (MyClass::*method)() = &MyClass::superPuperMethod;
который вызывается как
(myClassPtr->*method)()

Torvus
()
Последнее исправление: Torvus (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Напиши так:

QStringList EditorConfig::get_parameter_table(int version)
{
  switch (version) {
  case 1: return get_parameter_table_1();
  ...
  case 11: return get_parameter_table_11();
  }
  return QStringList();
}

void EditorConfig::update_version_process(void)
{
  const int fromVersion = get_config_version();

  for (int i = 1; i <= fromVersion; ++i) {
    const QStringList params = get_parameter_table(i + 1);
    if (params.isEmpty()) break;

    update_version(i, i + 1, get_parameter_table(i), params);
  }
}

tnodir
()
Последнее исправление: tnodir (всего исправлений: 1)

Можно, только это не такой указатель, к которым ты привык. Текст не первой свежести, но в целом описывает картину довольно хорошо: https://rsdn.ru/article/cpp/fastdelegate.xml

m0rph ★★★★★
()
Ответ на: комментарий от tnodir

Тут в двух местах приходится прописывать 11:

case 11: return get_parameter_table_11();

Забудешь, и получишь глюк.

А в моем решении добавляется строчка, в которой поменять номер нужно только в одном месте.

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Короче ты пытаешься решать сишными инструментами, то что в с++ решено уже и так. Используй выскоуровневые вещи и забудь о указателях на функции когда пишешь на с++ (правда для того же bind оно немного надо, ну там в принципе главное один раз понять как делать и все, всегда одинаково все). Они (указатели на функции) нужны в с++ только если подключаешь что-то сишное, или предоставляешь сишный интерфейс с с++ внутренностями.

Dudraug ★★★★★
()

«Указатель на метод» фактически не является указателем в том смысле, в котором им является, например, указатель на функцию.

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

Почему комитет принял такое решение - можешь попробовать уточнить у участников комитета или в публичных обсуждениях. В первую очередь это запрещено для корректной обработки виртуальных методов.

Для получения указателя на метод ты можешь использовать хак и завернуть его вызов в лямбду или через bind. Или поясни, зачем тебе нужен именно указатель на метод и чем не подходит std::function.

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 1)
Ответ на: комментарий от trex6

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

Да ладно? Раньше видел пример, что можно скастовать к указателю по смещению в vtable. Будет, это, конечно, говнокод и компилерозависимо, но чисто для эксперимента, чтобы понять как оно работать, сделать можно же.

UVV ★★★★★
()
Последнее исправление: UVV (всего исправлений: 1)
Ответ на: комментарий от Xintrea

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

Я думаю, что отсюда строятся все твои проблемы.

UVV ★★★★★
()
Ответ на: комментарий от Xintrea
class A {
public:
    void run()
    {
        vector<decltype(&A::foo)> v = { &A::foo, &A::bar, &A::baz };

        for (auto &f : v) {
            (this->*f)();
        }
    }

private:
    void foo()
    {
        cout << "foo called" << endl;
    }

    void bar()
    {
        cout << "bar called" << endl;
    }

    void baz()
    {
        cout << "baz called" << endl;
    }
};
vvviperrr ★★★★★
()

Чтобы не нарушать отчётностей.

anonymous
()
Ответ на: комментарий от vvviperrr

Вектор из std::function куда понятнее и красивее, чем то что я увидел выше;)

Dudraug ★★★★★
()
Ответ на: комментарий от Xintrea

Зачем плодить функции get_parameter_table_<version>()?

Почему нельзя в одной функции get_parameter_table(int version) всё это разрулить?

Покажи содержимое get_parameter_table_1() и get_parameter_table_11().

tnodir
()
Последнее исправление: tnodir (всего исправлений: 1)

то, что вы желаете, делается через std::function, либо массив указателей на функции, но это уже написали до меня. добавлю ещё вариант: массив функторов, объектов класса с перегруженным оператором (). в свою очередь, внутри функтора можно держать опять же либо указатель на функцию, либо действовать через наследование и объявить () виртуальным.

next_time ★★★★★
()
Ответ на: комментарий от redixin

Ну допустим можно получить указатель, но он один для всех инстансов. Потому и можно только для статик методов.

В этом то и проблема. Хоть обычный метод, хоть статик-метод, имеют единственное размещение кода в памяти. Ничего не стоит получить указатель на обычный метод, и вызвать его с передачей указателя на данные конкретного объекта в первом параметре. Почему этого не сделано, а даны какие-то странные ограничения типа «только статик метод» - мне неясно. Я имею в виду такое поведение в рамках одного инстанса объекта.

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 1)
Ответ на: комментарий от vvviperrr

vector<decltype(&A::foo)> v = { &A::foo, &A::bar, &A::baz };

Вот как ты так лихо определил тип по методу foo, а запихиваешь туда bar и baz? Просто потому что знаешь, что сигнатуры идентичны?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Еще раз, последний раз, спрашиваю почему ты смотришь на c-ctyle решения? Почему не хочешь использовать стандартные с++ решения вроде std::function?

Dudraug ★★★★★
()
Последнее исправление: Dudraug (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Если сигнатуры не идентичны, то компилятор выдаст ошибку. В чем проблема? Другое дело, что задача прямо создана для bind и все эти пляски тут вообще нафиг не нужны.

roof ★★
()
Ответ на: комментарий от Xintrea

Проблема в реализации. Дело в том что в C++ есть указатель на функцию (который по своей сути просто void*), а есть указатель на метод класса (&A::f) - и это уже не тривиальный указатель, а сложная структура с несколькими полями и флагами. Дело в том что может быть как минимум 3 разных случая указателя на метод класса:

1. Указатель на простой метод класса. В этом случае в струкутре стоит пометка что это указатель на стандартный метод класса и адресс этого метода (я так понял вы и хотите получить этот адресс, и его в принципе можно получить через служебный union - но реально это вам не нужно).

2. Указатель на виртуальный метод класса. В этом случае в структуре будет стоять пометка что это виртуальный метод + индекс этого метода в таблице виртуальных методов). Для этого случая получить адресс функции не получится без «this» Посмотрите внимательно на вот такой тестовый пример:

#include "stdio.h"                                                                                                                                                                           
                                                                                                                                                                                             
class A {                                                                                                                                                                                    
public:                                                                                                                                                                                      
  typedef void (A::*FPtr)();                                                                                                                                                                 
                                                                                                                                                                                             
  virtual void f1() {                                                                                                                                                                        
    printf("A::f1()\n");
  }
  void f2() {
    printf("A::f2()\n");
  }
};

class B: public A {
public:
  virtual void f1() {
     printf("B::f1()\n");
  }
  void f2() {
    printf("B::f2()\n");
  }
};

class C: public A {
public:
  virtual void f1() {
    printf("C::f1()\n");
  }
  void f2() {
    printf("C::f2()\n");
  }
};

int main(int argc, const char *argv[]) {
  A::FPtr p1 = &A::f1;
  A::FPtr p2 = &A::f2;

  A a;
  B b;
  C c;

  (a.*p1)();
  (a.*p2)();

  (b.*p1)();
  (b.*p2)();

  (c.*p1)();
  (c.*p2)();
}

output:
A::f1()
A::f2()
B::f1()
A::f2()
C::f1()
A::f2()
Тоесть p2 у нас однозначно указывает на адресс функции, а вот p1 каждый раз вычисляется в зависимости от this (поскольку это указатель на виртуальный метод).

3. Указатель на метод класса у которого более одного родителя (множественное наследование). В этом случае помимо плясок с виртуальными / не виртуальными методами еще нужно добавлять «this модификатор» тоесть ссылку на RTTI о том как должен видоизменятся this при вызове метода.

zaz ★★★★
()
Ответ на: комментарий от Xintrea

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

Передача указателя на конкретную структуру это все под капотом, и этого нет в стандарте языка. Решили что не нужно.

redixin ★★★★
()

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

ckotinko ☆☆☆
()

std::function + std::mem_fn

anonymous
()

И, наконец, полноценные указатели на методы класса:

class MyClass
{
int b;
public:
void func(int a)
{ cout<<a + b;
}
MyClass(int _b): b(_b){}
};


typedef void (*func_mc) (MyClass& mc, int a);

int main()
{   func_mc some_func_mc = reinterpret_cast<func_mc>(&MyClass::func);
    MyClass my_class(12);
    some_func_mc(my_class, 5);
    return 0;
}

Вывод программы: 17.

Вот только зачем, когда есть более элегантные решения?

next_time ★★★★★
()
Последнее исправление: next_time (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.