Вернуть результат из функции можно с помощью return
. Такой подход мы рассматривали, когда говорили об области видимости переменных в C++. Но предположим, что мы решили идти другим путем. Построим функцию таким образом, что основной результат возвращается через аргумент. При этом return
используется для передачи кода завершения операции.
Подобный прием очень часто используется в C-обертках над кодом C++. Хоть фактическая реализация немного отличается, принцип остается тот же. Даже если вы сами не будете создавать такие обертки, то наверняка встретитесь с C-библиотеками, которые так работают. Но вы уже будете готовы ко всему.
Дополнительное ограничение C-интерфейса заключается в том, что вы не можете создать экземпляр класса в собственном коде. Эта задача должна быть делегирована библиотеке.
Посмотрим, как это может выглядеть:
class MyClass; // В С-обертке мы бы создали здесь свой хэндл: typedef void HMyClass;
int libFunction( int x, MyClass* result ) {
int errorCode = 0;
// …
return errorCode;
}
Кажется, что все неплохо. Но у нас нет определения класса MyClass
(его быть и не должно). Вы не сможете создать его экземпляр. А функция libFunction()
ожидает получить в качестве второго выходного параметра указатель на реальный объект:
int main() {
MyClass c; // Эта строка вызовет ошибку компиляции!
int errorCode = libFunction( 94, &c );
return 0;
}
Хьюстон, у нас проблема! Но не волнуйтесь. Все легко исправить. Мы помним, что указатель - переменная, которая хранит адрес другой переменной. Намек понятен? В качестве такой переменной, на которую будет указывать указатель, станет указатель :) Немного запутанно, но на примере все выглядит яснее:
MyClass x; // Переменная типа MyClass, которую мы создать, к сожалению, не можем
MyClass* pX = &x; // Указатель на переменную типа MyClass
MyClass** ppX = &pX; // Указатель на указатель на переменную типа MyClass
*ppX; // Если разыменуем ppX, то получим то же самое, что и просто pX
**ppX; // А если разыменуем ppX дважды, то откатимся на исходную переменную x
Продолжать цепочку указателей на указатели можно бесконечно. Но обычно хватает двух уровней.
Класс указателя от определения типа не зависит. Он всего лишь хранит адрес, поэтому объявить MyClass* c
мы имеем право. Воспользуемся этим:
class MyClass;
int libFunction( int x, MyClass** result ) {
// Реализацию на самом деле мы не видим. Здесь она приводится для краткости
int errorCode = 0;
// …
return errorCode;
}
int main() {
MyClass* c = nullptr;
int errorCode = libFunction( 94, &c );
return 0;
}
Теперь все лучше. Мы создаем указатель и инициализируем его значением nullptr
(нулевой указатель в "никуда"). Передаем в libFunction()
указатель на созданный указатель. В libFunction()
уже выделяется память и создается нужный объект. Например, подобным образом:
int libFunction( int x, MyClass** result ) {
// …
*result = new MyClass;
// …
}
Но и очищать память должна будет сама библиотека. Для этого понадобится функция такого вида:
void libFree( MyClass* object ) {
// Реализацию мы вновь не можем видеть
delete object;
}
int main() {
MyClass* c = nullptr;
int errorCode = libFunction( 94, &c );
// …
libFree( c );
return 0;
}
Отмечу, что рассмотренный подход имеет существенный недостаток: мы не можем получить доступ к функциям-членам созданного объекта! И правда. Ведь у нас отсутствует определение класса. Поэтому библиотека должна будет сама предоставлять дополнительные функции доступа, что не очень удобно. По сути это приводит к дублированию интерфейсов и лишним зависимостям. А так же явно нарушает принцип DRY. Но такова цена за хорошую переносимость.
Интересно!
Элина
побольше бы таких сайтов. Все подробно и доступно описано