C непрозрачный указатель

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

typedef void * OpaqueObject

В библиотеке:

OpaqueObject CreateObject()
{
   return new OurCppLibrary::Object();
}

Это, конечно, не обеспечивает абсолютно никакой безопасности типов для клиентов этой библиотеки. Должно ли изменение typedef с указателя void на указатель структуры работать точно так же, но обеспечивать небольшую безопасность типов?

typedef struct OpaqueObjectInternal_ *OpaqueObject 
// OpaqueObjectInternal_ is NEVER defined anywhere in client or library code

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


person Cat Zimmermann    schedule 14.03.2011    source источник
comment
В C нет оператора с именем new !!   -  person Mahesh    schedule 15.03.2011
comment
@Mahesh: я думаю, что OP оборачивает библиотеку C в C ++; new имеет место в C++ (выведено из квалификатора OurCppLibrary)   -  person fbrereto    schedule 15.03.2011
comment
void * имеет смысл как непрозрачный объект — ваша цель не связываться с объектом, и они знают, что с ним делать. Во всяком случае, создание void * делает его более безопасным, если вы просто используете его с их API, как описано.   -  person James    schedule 15.03.2011
comment
@James: я не понимаю, как удаление информации о типе делает ее более безопасной? Рассмотрим этот пример: void some_api_func(void*); int* i = ...; some_api_func(i); // oops!. Этого не может быть с безопасностью типов.   -  person GManNickG    schedule 15.03.2011
comment
@GMan - вы неправильно использовали API, и это довольно легко обнаружить. Пока вы вызываете их функции, используя их «непрозрачные» указатели, все в порядке. Если бы они раскрывали тип, у вас могло бы возникнуть искушение манипулировать объектами, которые они возвращают, когда вы никогда не должны были этого делать. Если это void *, таких иллюзий нет.   -  person James    schedule 15.03.2011
comment
Я оборачиваю реализацию C++ интерфейсом C.   -  person Cat Zimmermann    schedule 15.03.2011
comment
@James: мне бы очень хотелось, чтобы компилятор поймал как можно больше ошибок. Если вам дали ObjectInternal_*, что вы собираетесь с ним делать? Я думаю, что гораздо более вероятно, что вы передадите не тот объект, чем сделаете что-то с типом, у которого нет внутренних компонентов или методов, которые его используют.   -  person Cat Zimmermann    schedule 15.03.2011
comment
@Cat: Подожди, ты оборачиваешь C++ в C или наоборот? Если вы оборачиваете их библиотеку, вы можете обойтись без передачи структуры OpaqueObject по значению вашим функциям, которые содержат указатель внутри, и переадресовывает вызов метода. Затем компилятор должен поймать любые ошибки типа.   -  person James    schedule 15.03.2011
comment
@James: Вы неправильно использовали API... В самом деле, и когда, по-вашему, лучше всего это выяснить? Во время компиляции через проверку типов или во время выполнения (за день до релиза) из-за неопределенного поведения и сбоя? И я не понимаю смысла вашего второго отказа: вся цель непрозрачного указателя состоит в том, чтобы не раскрывать определение типа.   -  person GManNickG    schedule 15.03.2011
comment
@GMan: Так что не совсем безопасно, но я использовал множество библиотек с void * непрозрачными объектами и никогда не передавал неверный тип. Возможно, это мой опыт затуманивает мое представление об уровне безопасности, но я видел, что такая незначительная проблема, как правильное имя переменной, обычно предотвращает передачу неправильного указателя. Несмотря на это, см. Мой второй комментарий выше, чтобы узнать, что я считаю лучшим решением.   -  person James    schedule 15.03.2011
comment
@James: Я оборачиваю C++ в C. И обертка C, и C++ на данный момент являются устаревшими, и никто в моей команде не хочет вносить большие изменения, поскольку нет модульного тестирования. Прямо сейчас я просто хочу почти ничего не делать для обеспечения безопасности типов; эти определения типов генерируются из IDL, поэтому это тривиально.   -  person Cat Zimmermann    schedule 15.03.2011
comment
@James: Хорошо, это хорошо для вас, но остальные из нас предпочли бы правильно использовать компилятор и языковые функции, доступные нам, чтобы лучше писать правильный код. (Я никогда не понимал этого, учитывая выбор между: если вы потерпите неудачу, это потому, что вы отстой; улучшить и, хотя некоторые могут обойтись без царапин, люди совершают ошибки, и мы можем защитить от этого бесплатно, люди выбирали первое, я думал, что мы прошли век элитарности. ;))   -  person GManNickG    schedule 15.03.2011
comment
@GMan: Элитарность... Элитарность никогда не меняется. :) Кроме того, «Если ты терпишь неудачу, это потому, что ты отстой» - это то, что мне говорили о программировании на C++ с тех пор, как я начал... :(   -  person James    schedule 15.03.2011


Ответы (3)


Подвохов нет; эта форма предпочтительнее именно из-за безопасности типов.

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

person GManNickG    schedule 14.03.2011

На самом деле класс C++ также является структурой C. Итак, вы можете просто сделать это:

struct Object;
struct Object* Object_Create(void);

И оболочка C в библиотеке таким образом:

struct Object* Object_Create(void)
{
    return new Object;
}

Вызов метода может выглядеть так:

uint32_t Object_DoSomething( struct Object* me, char * text )
{
    return me->DoSomething( text );
}

Я пробовал это, и я не вижу никаких недостатков.

person Robert    schedule 16.03.2012

Первое, на что следует обратить внимание в предлагаемом вами курсе, — это то, что вы сообщаете другим, которым, возможно, придется поддерживать код, который вы пишете. Прежде всего, непрозрачные объекты — это способ C показать пользователю библиотеки, что сопровождающий библиотеки не дает абсолютно никаких гарантий в отношении реализации объекта, кроме того, что этот объект можно использовать с задокументированными функциями. Удаляя void*, вы, по сути, объявляете миру: «Я объявляю эту реализацию, которая когда-то была непрозрачной, стабильной». Это может быть не то, что вы хотите.

Во-вторых, ИМХО, которое вы предлагаете, - это половинчатое решение, которое никого не устраивает, лучший подход - разработать класс-оболочку. Это имеет дополнительное преимущество, позволяя вам обернуть функции инициализации и уничтожения, которые неизбежно сопровождают непрозрачные объекты в стиле C, в конструкторах и деструкторах вашего класса. Это позволит вам обеспечить управление ресурсами, а также безопасность типов для ваших клиентов без гораздо большей работы.

person ltc    schedule 15.03.2011
comment
Я не понимаю, почему указатель структуры не непрозрачен. Нет определения структуры, к которой может получить доступ клиент (или реализации, если уж на то пошло). Что касается класса-оболочки, это для потребителя на чистом C. - person Cat Zimmermann; 15.03.2011
comment
Похоже, меня смутил тот факт, что в примере используются конструкции C++, а в заголовке написано C++. Кроме того, я бы не рекомендовал называть структуру OpaqueObjectInternal_ как Internal, так и завершающим символом подчеркивания, которые являются общими соглашениями, указывающими на частные реализации. Недостаточное чтение комментариев привело меня к мысли, что эта структура была внутренней реализацией, а не непрозрачным указателем. Возможно, здесь есть урок для нас обоих ;-) - person ltc; 16.03.2011
comment
Кто-то отредактировал заголовок с C на C++. Как бы вы назвали структуру? Для каждого типа, о котором знает интерфейс C, существует отдельная структура. - person Cat Zimmermann; 17.03.2011
comment
Я бы просто отключил Internal_. Иногда людям нравится делать что-то вроде typedef struct typename_opaque_t * typename_handle; но это действительно зависит от вас. - person ltc; 17.03.2011