Расслабьтесь с преобразованием void * в C ++

В языке C приведение указателей к void * и от него не является ошибкой.

Основным препятствием при переносе на C ++ является необходимость приводить указатели при возврате из функций, имеющих дело с универсальными указателями, такими как malloc, и функций, объявленных в моем собственном коде, например void *block_get(Blkno const blkno);.

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

Моя справочная ошибка следующая:

struct Cpfs *cpfs = calloc(1, sizeof(*cpfs));

который в MSVC производит:

Ошибка 2, ошибка C2440: «инициализация»: невозможно преобразовать из «void *» в «Cpfs *» e: \ src \ cpfs \ cpfs.c 179

Очевидно, я не могу использовать new или static_cast, которые, естественно, использовал бы, если бы я больше не использовал C. Как лучше всего обеспечить максимальную безопасность типов для void * для каждого языка с минимальной многословностью?


person Matt Joiner    schedule 03.10.2010    source источник
comment
Зачем вам нужно портировать на общее подмножество C и C ++? Как вы обнаружили, это сильно ограничивает то, что C (как альтернатива C ++) вы можете использовать. Почти во всех средах, в которых доступен C ++, также доступен C, а также можно связывать объектные файлы C ++ и C. Я не могу понять, какую выгоду вы получите, перенеся на общее подмножество.   -  person CB Bailey    schedule 03.10.2010
comment
Для обеспечения безопасности типа в C (что немного сложно) создайте функцию для каждой структуры, задача которой - выделить и вернуть указатель на новую структуру. Аналогичным образом создайте функцию для освобождения структуры.   -  person rwong    schedule 03.10.2010
comment
Я хотел бы знать причину попытки написать программу, которая компилируется для двух разных языков. Обычно это что-то остается для головоломок.   -  person GManNickG    schedule 03.10.2010
comment
Кстати, в С ++ вы должны использовать static_cast для преобразования в / из void*.   -  person jamesdlin    schedule 03.10.2010
comment
@GMan: когда я это делал, всегда были static inline (или определяемые реализацией эквиваленты, в случае C89) функции в заголовках, относящиеся к библиотеке, в основном написанной на C, но используемой обоими. Когда становится слишком сложно работать на пересечении, вы можете отказаться от этой функции как static inline и просто написать ее на C.   -  person Steve Jessop    schedule 03.10.2010
comment
В 9 случаях из 10 static inline в файлах заголовков - это ложная преждевременная оптимизация.   -  person R.. GitHub STOP HELPING ICE    schedule 04.10.2010
comment
@Р. К счастью, я делал это только в 10% случаев и знаю свою кодовую базу лучше, чем вы.   -  person Steve Jessop    schedule 05.10.2010


Ответы (5)


Может как то так? (не проверено, компилятор отсутствует, макросы используются не очень часто):

#ifdef __cplusplus
    #define pointer_cast(type, pointer) reinterpret_cast<type>(pointer)
#else
    #define pointer_cast(type, pointer) (type)(pointer)
#endif
person Milan    schedule 03.10.2010
comment
Почему бы просто не избежать бесполезного приведения типов, когда __cplusplus не определен? - person R.. GitHub STOP HELPING ICE; 04.10.2010
comment
Я закончил тем, что создал макрос VOID_CAST, который приводил только при необходимости. - person Matt Joiner; 21.02.2011

Я бы предложил либо просто использовать приведение в стиле C, либо обернуть приведение в макрос, который либо не расширяется до нуля (в C), либо static_cast в C ++.

person Hasturkun    schedule 03.10.2010
comment
reinterpret_cast из void* - не лучший состав. static_cast достаточно и с меньшей вероятностью незаметно приведёт недопустимый источник к типу указателя. - person CB Bailey; 03.10.2010

Если ваш компилятор поддерживает decltype(), вы можете использовать некоторую магию макросов, чтобы избежать явного повторения имени типа (и, благодаря sizeof, размера элемента):

#ifdef __cplusplus
#define my_calloc(VAR, COUNT) \
    static_cast<decltype(VAR)>(std::calloc(COUNT, sizeof *VAR))
#else
#define my_calloc(VAR, COUNT) calloc(COUNT, sizeof *VAR)
#endif

Пример использования:

#ifdef __cplusplus
#include <cstdlib>
#else
#include <stdlib.h>
#endif

struct Cpfs *cpfs = my_calloc(cpfs, 42);

Более чистым решением, вероятно, было бы просто использовать компилятор C и связать объектные файлы, хотя ...

person Christoph    schedule 03.10.2010
comment
decltype похож на typeof, использование компилятора c не вариант, msvc не поддерживает c99. c ++ компилирует его лучше, чем неполный компилятор c - person Matt Joiner; 03.10.2010
comment
@Matt: вы можете использовать MinGW для компиляции части C вашего проекта и по-прежнему использовать MSVC для части C ++ (см., Например, stackoverflow.com/questions/2096519/) - person Christoph; 03.10.2010
comment
@Matt Joiner: программирование на пересечении C99 и C ++ существенно не отличается от простого программирования на C89, оно просто более неудобно и раздражает. Вы пишете на неполноценную версию C как есть, использование неполноценного компилятора C ничуть не хуже, и, по крайней мере, C89 является стандартом. - person Steve Jessop; 03.10.2010

создайте заменяющую функцию распределителя, которую вы можете определить по-разному для сборок C и C ++: - Что-то вроде этого в файле заголовка:

#ifdef __cplusplus
template<typename TypeT>
TypeT* MyAlloc(TypeT** pOut,size_t cb){
  *pOut = static_cast<TypeT*>(malloc(cb)); //aint c++ pretty.
  return *pOut;
}
#else
  extern void* MyAlloc(void** ppv, size_t cb);
#endif

Теперь у вас есть в сборках C ++ функция, которая может сделать вывод о типе вещей, с которыми она имеет дело, а в сборках C это обычная функция, возвращающая void *.

Единственная проблема - это необходимость передать указатель для выделения - компилятор C ++ не будет пытаться вывести параметр шаблона на основе только возвращаемого типа функции afaik. Вы могли бы назвать это агностически так:

int *p;
if(MyAlloc(&p,sizeof(int)*n)){
  ...
person Chris Becke    schedule 03.10.2010
comment
// это не С ++ красиво. Да, когда вы используете new. - person GManNickG; 03.10.2010
comment
new еще хуже, поскольку компиляторы c ++ не выводят параметры шаблона класса из вызова конструктора. Так что на днях мне пришлось написать что-то вроде этого: SomeClass<int(int)>* pWrapper = new SomeOtherClass<int(SomeOtherClass::*)(int)>(this,&SomeOtherClass::method); - person Chris Becke; 03.10.2010
comment
@Chris: Вы используете new, не помещая его в оболочку, ваша точка зрения для меня спорна. В C ++ 0x есть auto, и ваш ответ в любом случае - отвлекающий маневр. - person GManNickG; 04.10.2010
comment
Теперь лучше всего всегда связывать вызовы с новым в оболочке? auto решает только половину проблемы. и new не более красив, чем malloc (), особенно если рекомендуемый способ избежать уродства - обернуть его. - person Chris Becke; 04.10.2010
comment
@ Крис: Ага. Это даже не новая идея, она относительно старая. (Как, например, мы были в современную эпоху C ++, когда вы не управляете своими ресурсами вручную. Оберните их (std::vector, shared_ptr и т. Д.); Это управление ресурсами с привязкой к области видимости (SBRM), более известное как уродливое name RAII) Каждый раз, когда вам нужно что-то освободить вручную, вы сделали что-то не так. Опять же, эта касательная - отвлекающий маневр. Код, который вы назвали некрасивым в своем сообщении, уродлив только потому, что это не идиоматический C ++, ответ каким-то другим кодом не имеет значения. - person GManNickG; 05.10.2010

Единственное известное мне решение - это явное приведение типов:

struct Cpfs *cpfs = (Cpfs*)calloc(1, sizeof(*cpfs));

Здесь оба компилятора довольны. Также помните, что для старых компиляторов malloc может возвращать char *.

hth

Марио

person Mario The Spoon    schedule 03.10.2010