Как передать класс в функцию c
Каким, наиболее простым, способом можно передать указатель на ссылку fb в качестве параметра функции A::fa? В предпоследней строке ошибка, я понимаю,что в качестве параметра нужно указывать void(*f)(), а я пихаю void(B::*f)().
class A < public: void fa(void(*fa1)())<>>; class B < public: A a; void fb()<>void fb2() < a.fa(&fb) >>;
Вообще хочу организовать обход дерева и при этом для каждого узла вызвать определенную внешнюю функцию.
Последний раз редактировалось KemanSR; 21.07.2015 в 21:54 .
Регистрация: 13.07.2012
Сообщений: 6,488
class B;
void fa(void(B::*fa1)())<>
Форумчанин
Регистрация: 03.05.2010
Сообщений: 129
Прошу прощения, я не до конца сформулировал вопрос. Мне нужно чтобы в классе А не было известно об В. Задача обойти дерево и вывести элементы в CTreeCtrl. Предполагаю сделать метод в классе Doc вывода одного элемента и передать его (метод) в качестве аргумента в класс дерева. А там рекурсией обойти все дерево. Мысль такая: дерево — данные, определенной структуры, зачем же тут думать об их отображении.
Последний раз редактировалось KemanSR; 22.07.2015 в 20:42 .
Регистрация: 16.12.2011
Сообщений: 2,329
//Title of this code //g++ 4.9.2 #include #include struct first < typedef std::functionevent; event setMethod; template void set(F method, O& obj) < setMethod = std::bind( method, std::ref(obj) ); >void work() < if(setMethod) setMethod(); else std::cout>; struct second < void work()< std::coutvoid operator()() < work(); >>; int main() < std::cout ; f.work(); // можно так: // у класса second должен быть operator() f.setMethod = s; f.work(); //можно так: f.set( &second::work, s ); f.work(); >
Форумчанин
Регистрация: 03.05.2010
Сообщений: 129
Не дотягиваю до этого уровня, т.к. ранее не работал с функциональными объектами. Хорошо, применю менее правильный, но более простой способ. Спасибо за ответы.
Последний раз редактировалось KemanSR; 22.07.2015 в 21:32 .
| Похожие темы | ||||
| Тема | Автор | Раздел | Ответов | Последнее сообщение |
| Вызов функции из одного класса кнопкой из другого класса | lexflax | Qt и кроссплатформенное программирование С/С++ | 1 | 12.12.2014 21:48 |
| .NET 4.x Передача в новый поток метода с параметрами из другого класса | maxspace | C# (си шарп) | 1 | 25.03.2013 14:26 |
| JList Listener непонятки c вызовом метода из другого класса | alekola | Общие вопросы по Java, Java SE, Kotlin | 1 | 22.11.2012 02:12 |
| получение ссылки на функцию из класса шаблона и передача ее как параметр шаблона | pror0ck | Общие вопросы C/C++ | 7 | 17.06.2012 15:06 |
| Объект одного класса в конструкторе другого | Benderbej | PHP | 5 | 13.02.2011 22:48 |
Как передать класс в функцию c
Аргументы, которые представляют переменные или константы, могут передаваться в функцию по значению (by value) и по ссылке (by reference).
При передаче аргументов по значению функция получает копию значения переменных и констант. Например:
#include void square(int); // прототип функции int main() < int n ; std::cout void square(int m) < m = m * m; // изменяем значение параметра std::cout Before square: n = 4 In square: m = 16 After square: n = 4
Почему так происходит? При компиляции функции для ее параметров выделяются отдельные участки памяти. При вызове функции вычисляются значения аргументов, которые передаются на место параметров. И затем значения аргументов заносятся в эти участки памяти. То есть функция манипулирует копиями значений объектов, а не самими объектами.
Передача параметров по ссылке
При передаче параметров по ссылке передается ссылка на объект, через которую мы можем манипулировать самим объектов, а не просто его значением. Так, перепишем предыдущий пример, используя передачу по ссылке:
#include void square(int&); // прототип функции int main() < int n ; std::cout void square(int& m) < m = m * m; // изменяем значение параметра std::cout по ссылке. Ссылочный параметр связывается непосредственно с объектом, поэтому через ссылку можно менять сам объект. То есть здесь при вызове функции параметрmв функцииsquareбудет представлять тот же объект, что и переменнаяnИ если мы скомпилируем и запустим программу, то результат будет иным:
Before square: n = 4 In square: m = 16 After square: n = 16Передача по ссылке позволяет возвратить из функции сразу несколько значений. Также передача параметров по ссылке является более эффективной при передаче очень больших объектов. Поскольку в этом случае не происходит копирования значений, а функция использует сам объект, а не его значение.
От передачи аргументов по ссылке следует отличать передачу ссылок в качестве аргументов:
#include void square(int); // прототип функции int main() < int n = 4; int &nRef = n; // ссылка на переменную n std::cout void square(int m) < m = m * m; // изменяем значение параметра std::cout Before square: n = 4 In square: m = 16 After square: n = 4Передача параметров по значению больше подходит для передачи в функцию небольших объектов, значения которых копируются в определенные участки памяти, которые потом использует функция.
Передача параметров по ссылке больше подходит для передачи в функцию больших объектов, в этом случае не нужно копировать все содержимое объекта в участок памяти, за счет чего увеличивается производительность программы.
Преобразования типов
Передача параметров по значению и по ссылке отличаются еще одним важным моментом. С++ может автоматически преобразовывать значения одних типов в другие, в том числе если подобные преобразования сопровождаются потерей точности (например, преобразование от типа double к типу int). Но при передаче параметров по ссылке неявные автоматические преобразования типов исключены. Так, рассмотрим пример:
#include void printVal(int); void printRef(int&); int main() < double value; printVal(value); // 3 printRef(value); // ! Ошибка > void printVal(int n) < std::cout void printRef(int& n)
Здесь определены две практически идентичные функции. Только функция printVal получает параметр по значению, а функция printRef - по ссылке. При вызове в обе функции передается число типа double . Но параметр обоих функций представляет тип int . И если при передаче по значению переданное число double успешно преобразуется в int (пусть и с потерей точности), то при передаче по ссылке мы столкнемся с ошибкой на этапе компиляции. Это еще одна причина, почему нередко рекомендуется передавать значения по ссылки - исключается вероятность предвиденных и иногда нежелательных преобразований типов.
Указатели на методы классов в C++
Решил написать статью об указателях на методы классов. Недавно мне пришлось столкнуться с тем, как они работают изнутри, когда писал некоторые вещи ориентированные под компилятор. Эти указатели работают не совсем как обычные указатели, не имеют возможности быть приведенными в void, и часто имеют размер больше 8 байт. Информации на эту тему в интернете я нашел относительно немного, потому решил разобраться сам.
Особенно пугают такие страшилки, которые мало что объясняют о том как происходит на самом деле и почему, а лишь пытаются приучить программиста слепо следовать требованиям.
Давайте разберемся что и почему происходит.
Все манипуляции будут произведены для архитектуры x86-64.Взглянем на код.
#include int main()Размер указателя на метод больше 8 байт. В некоторых компиляторах это не так, например компилятор Microsoft ужимает до 8 байт указатель на метод в некоторых случаях. В последних версиях компиляторов clang и gcc для Linux принимал размер 16 байт.
Как мне кажется, разработчики компиляторов не могли без особой причины заменить обычный указатель на нечто другое. Давайте же разберемся почему они так сделали.
Посмотрим такой код на C++. Это базовый пример вызова метода из указателя на метод.
struct A; typedef void (A::*Ptr) (); Ptr ptr; void call(A *a) < (a->*ptr)(); >Скомпилировав код такой командой:
clang++ code.cpp -c -emit-llvm -S -O3 -fno-discard-value-namesПолучаем вывод LLVM IR:
@ptr = dso_local local_unnamed_addr global < i64, i64 >zeroinitializer, align 8 ; Function Attrs: uwtable define dso_local void @_Z4callP1A(%struct.A* %a) local_unnamed_addr #0 < entry: %.unpack = load i64, i64* getelementptr inbounds (< i64, i64 >, < i64, i64 >* @ptr, i64 0, i32 0), align 8, !tbaa !2 %.unpack1 = load i64, i64* getelementptr inbounds (< i64, i64 >, < i64, i64 >* @ptr, i64 0, i32 1), align 8, !tbaa !2 %0 = bitcast %struct.A* %a to i8* %1 = getelementptr inbounds i8, i8* %0, i64 %.unpack1 %this.adjusted = bitcast i8* %1 to %struct.A* %2 = and i64 %.unpack, 1 %memptr.isvirtual.not = icmp eq i64 %2, 0 br i1 %memptr.isvirtual.not, label %memptr.nonvirtual, label %memptr.virtual memptr.virtual: ; preds = %entry %3 = bitcast %struct.A* %this.adjusted to i8** %vtable = load i8*, i8** %3, align 1, !tbaa !5 %4 = add i64 %.unpack, -1 %5 = getelementptr i8, i8* %vtable, i64 %4, !nosanitize !7 %6 = bitcast i8* %5 to void (%struct.A*)**, !nosanitize !7 %memptr.virtualfn = load void (%struct.A*)*, void (%struct.A*)** %6, align 8, !nosanitize !7 br label %memptr.end memptr.nonvirtual: ; preds = %entry %memptr.nonvirtualfn = inttoptr i64 %.unpack to void (%struct.A*)* br label %memptr.end memptr.end: ; preds = %memptr.nonvirtual, %memptr.virtual %7 = phi void (%struct.A*)* [ %memptr.virtualfn, %memptr.virtual ], [ %memptr.nonvirtualfn, %memptr.nonvirtual ] tail call void %7(%struct.A* %this.adjusted) ret void >LLVM IR является промежуточным представлением между машинным кодом и C++ в компиляторе Clang. Он позволяет компилятору производить оптимизации не зависящие от конкретной архитектуры процессора, а нам он дает понять что происходит на тех или иных стадиях компиляции, и является более читаемым чем язык ассемблера.
Подробнее про LLVM IR можно узнать в Википедии, официальном сайте LLVM и Clang.
- Взглянув на первую строчку, видно что указатель на метод является структурой `< i64, i64 >`, а не обычным указателем. Эта структура содержит два i64 элемента, которые могут уместить в себя 2 обычных указателя. Видно почему мы не можем приводить указатели на методы в обычные. Мы не можем без потерь преобразовать 16 байт в 8 байт в общем случае.
- В блоке `entry`, начинающимся с 5 строки, видно что происходит корректирование указателя `this`. Это значит, что компилятор прибавляет к указателю на `this` значение второго элемента этой структуры, и позже в блоке `memptr.end` передает его в вызов метода.
- Нечто странное происходит происходит в блоке `entry` на 14 строке с первым элементом структуры. Компилятор вычисляет выражение аналогичное следующему: `bool isvirtual = val & 1`. Компилятор считает указатель на метод виртуальным, если число в нем нечетное, в противном случае невиртуальным.
- Если указатель на метод указывает на невиртуальный метод, то значение первого элемента считается обычным указателем на функцию, который позже вызывается. Эти предположения происходят в блоке `memptr.nonvirtual`.
- Если указатель на метод указывает на виртуальный метод, то тут сложнее. Вначале вычитается единица из первого элемента структуры, и вычисленное значение является отступом для виртуальной таблицы, указатель на которую берется из значения указателя на `this`. Это происходит в блоке `memptr.virtual`.
- Информацию является ли он виртуальным
- Указатель на адрес метода (если не виртуальный)
- Смещение в vtable (если виртуальный)
- корректирование `this`
Метод класса имеет невидимый первый параметр — указатель на `this`, который передается компилятором при вызове метода. Остальные аргументы передаются после в том же порядке, что и были.
Если бы мы писали этот код на C++, то он выглядел бы примерно так:
A *a; a->method_name(1, 2, 3); method_name(a, 1, 2, 3);
Чтобы разобраться с значением корректирования, рассмотрим следующий пример:
struct A < char a[123]; >; struct B < char a[0]; void foo(); static void bar(B *arg); >; struct C : A, B <>; void (C::*a)() = &C::foo; void (C::*b)() = &B::foo; void (B::*c)() = &B::foo; void (*a1)(C*) = &C::bar; // error void (*b1)(C*) = &B::bar; // error void (*c1)(B*) = &B::bar; // ok
Как мы видим, тут представлены примеры указателей на методы и аналогичные функции, которые принимают указатель на класс как указатель на `this`. Однако компилятор не может преобразовать указатели a1 и b1 в связи с тем, что мы не можем бесплатно преобразовывать указатели дочернего типа в указатели родительского типа. Компилятору необходимо запомнить отступ (значение корректирования) внутри дочернего класса для родительского класса и сохранить его где-то.
Посмотрим такой код:
struct A < char a[123]; >; struct B < char a[0]; void foo(); static void bar(B *arg); >; struct C : A, B <>; void (C::*a)() = &C::foo; void (C::*b)() = &B::foo; void (B::*c)() = &B::foo;
Скомпилируем код командой:
clang++ code.cpp -c -emit-llvm -S -O3 -fno-discard-value-names
@a = dso_local global < i64, i64 >< i64 ptrtoint (void (%struct.B*)* @_ZN1B3fooEv to i64), i64 123 >, align 8 @b = dso_local global < i64, i64 >< i64 ptrtoint (void (%struct.B*)* @_ZN1B3fooEv to i64), i64 123 >, align 8 @c = dso_local global < i64, i64 >< i64 ptrtoint (void (%struct.B*)* @_ZN1B3fooEv to i64), i64 0 >, align 8
Видно, что указатель на метод указывает на одну и ту же функцию. Однако значение корректирования разное из-за того что класс B расположен по сути внутри класса C.
Компилятору C++ необходимо знать отступ от базового класса для того чтобы передать `this` в метод класса.
Что плохого в этой реализации:
- Размер указателя относительно большой, даже если корректирование отсутствует каким либо образом в gcc и clang
- Каждый раз идет проверка виртуальности метода, даже если мы знаем что он не виртуальный
- Использовать статический метод, принимающий экземпляр класса
- Забыть про существование указателей на методы, и решить проблему как-то иначе в прочих случаях
- В интернете есть советы использовать std::bind, std::function и подобные библиотечные функции. Проверив их поведение, я не обнаружил существования каких либо оптимизаций для указателей на методы.
- У меня нет технической возможности проверить что происходит в компиляторах Microsoft, поэтому не особо про них рассказал. Однако протестировав онлайн компиляторы, я заметил что MSVC умеет анализировать структуру классов и удалять поле значения корректирования, если оно не требуется.
#include #include struct A; extern A* a; extern void(A::*func)(); template T assume_not_virual(T input) < struct Ptr < uint64_t a, b; >; static_assert(sizeof(T) == sizeof(Ptr), ""); Ptr ptr; memcpy(&ptr, &input, sizeof(input)); __builtin_assume(!(ptr.a & 1)); return input; > void call() < (a->*assume_not_virual(func))(); >
В данном примере компилятор не будет проверять метод на виртуальность, и сразу вызовет его как невиртуальный. Это лишь пример необычной оптимизации, не стоит использовать его в реальности.
Также я написал маленькую программку, что выводит данные об указателях на методы и помогает понять их внутренности, пока писал эту статью. Работает в clang и gcc под Linux. Код выложен тут.
Проведя это маленькое расследование, я понял как работают указатели на методы и как с ними жить. Надеюсь, это оказалось полезно для кого-то.
Как передать объект класса по ссылке?
Если Вы «пытаетесь познать C++», то хотя бы книгу откройте по языку. Любую. Тогда таких вопросов появляться не будет.
21 июн 2016 в 18:02
Зря @ixSci читаете нравоучения. Q&A не для дискуссий и высоких материй. Вообще, сходу не нашел ответа на вопрос для Си++ в базе вопросов, так что сам по себе вопрос считаю адекватным, вот первый ответ, близкий по духу. Формулировка, вопроса конечно страдает, требуется правка. Лучше переформулируйте вопрос за автора, он просто не знает, что в нем написать. Думаю, против ни кто не будет.
21 июн 2016 в 18:41
Не понимаю, с чего вдруг Вы тут разводите цензуру. Не нравится вопрос - поднимайте тревогу, пусть модераторы решают. Совсем не обязательно дергать самолюбие авторов. Я вообще такие вопросы пропускаю мимо ушей. Этот привлек внимание как раз отрицательной оценкой.
22 июн 2016 в 8:04
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Передача объекта по ссылке используется достаточно часто и описана во многих книгах по С++, однако не всем получается быстро понять данный механизм.
Для начала рассмотрим принимающую сторону, т.е. функцию или метод класса, в который передается ссылка на объект. В простейшем случае это выглядит так:
void foo(ObjectType& object)
К типу передаваемого объекта добавляется символ & , причем его можно размещать как вплотную к типу, так и к имени объекта. Это зависит от стиля программирования.
При передачи объекта по ссылке не происходит копирования, т.е. мы по сути передаем адрес на объект, что быстрее и менее затратно по памяти. Плюс ко всему мы работаем непосредственно с объектом, переданным по ссылке. Если же объект исключительно входной и не должен быть изменен, то следует добавить модификатор const:
void foo(const ObjectType& object)
Теперь, изменить объект object в функции не получится. Чтобы было возможно вызывать методы класса object они должны быть объявлены как const . Следует отметить, что есть понятие "ссылки на константу" а есть "константная ссылка". В данном случае мы имеем дело с первым понятием.
Теперь перейдем непосредственно к передаче объекта. Тут есть три варианта:
ObjectType myObject; foo(myObject);
ObjectType* myObject = new ObjectType(. ); foo(*myObject);`
ObjectType& myObject = otherObject; foo(myObject);