У меня есть системный вызов, подобный следующему:
int transfer(int handle, int direction, unsigned char *data, int length);
Я написал следующие две функции:
int input(int handle, void* data, int length)
{
return transfer(handle, 1, static_cast<unsigned char*>(data), length);
}
int output(int handle, const void* data, int length)
{
return transfer(handle, 0, static_cast<unsigned char*>(const_cast<void*>(data)), length);
}
Мне не нравится вложенный const_cast внутри static_cast, есть ли способ выполнить преобразование из const void* в unsigned char* за один шаг?
Единственное преобразование C++, которое может удалить const, - это const_cast, даже reinterpret_cast (который позволяет вам попробовать преобразовать практически все) не позволит вам отбросить const. Одношаговое приведение, которое будет работать, - это приведение C. Он разрешился бы в одну и ту же (или почти) серию приведений, но, похоже, это было бы одно применение. Я бы рекомендовал вести длинные серии забросов.
это на самом деле функция вывода const исправьте ваш?
@Moia системная функция transfer довольно уродлива, но она не изменяет переданный буфер, если direction - это 0 (вывод).
Что ж, читая ваши комментарии, вы должны адаптироваться к тому, что плохо спроектировано и что вы не можете изменить. Я думаю, у вас нет другого выхода, кроме как использовать приведение в стиле C или скопировать data в буфер unsigned char, который вы бы вместо этого передали в transfer(). Вы, вероятно, не захотите этого делать.
const в const void* data существует только для того, чтобы вы не изменяли data внутри своей функции. Но если вы отбросите его, сигнатура функции может быть с таким же успехом int output(int handle, void* data, int length). Или лучше int output(int handle, unsigned char* data, int length).
@JHBonarius функции input и output имеют правильные параметры, они существуют для того, чтобы скрыть некрасивую и плохую сигнатуру функции transfer.
@ Wizard79, но это всего лишь мираж, поскольку вы сразу же выбросили const. Внешне вас просто «обманули» тем, что есть гарантия, что переданный вами параметр не будет изменен. Как я сказал в своем ответе: что, если transfer действительно изменит значение? Тогда вы потенциально можете иметь необъяснимое повреждение данных, отладка которого может занять много времени. Я бы не стал скрывать эту подпись, переделывая все, потому что вы вводите пользователя в заблуждение. Вместо этого сделайте копию или около того, как я предлагаю в своем ответе.
@JHBonarius, это не мираж, это договор. Контракт соблюдается, поскольку буфер, переданный в transfer, не изменяется, если direction равен 0. Сам const не транслируется ни в какой код, это сам контракт. Я считаю, что ваше предложение сделать копию очень плохое: оно беспричинно влияет на производительность и память.
@ Wizard79 Сейчас это может быть так, но что, если кто-то изменит код transfer (или какой-либо сопоставимой функции в аналогичной ситуации). Например, теперь переупорядочивание данных в data для оптимальной связи: тогда у вас внезапно есть изменение в ваших исходных данных. Итак, вы можете быть уверены, что поступаете правильно, но по ходу дела делаете много предположений. За свою карьеру я отлаживал много старого кода, где дизайнеры делали такие предположения, которые через x лет оказались ошибкой. const_cast может быть очень опасным.
@JHBonarius, если и когда transfer будет делать что-то столь же глупое, как изменение выходного буфера, output будет соответственно изменен. Между прочим, вот что происходит внутри glibc write: квалификатор const для переданного буфера автоматически отбрасывается при передаче в syscall. Вы должны доверять контракту, буфер не будет изменен внутри ядра для этого системного вызова.
Даже контракты могут меняться со временем. Я видел ошибки ... В этом случае я ожидал бы, что поставщик API предоставит подходящие интерфейсы transmit и receive вместо одного кольца, чтобы управлять ими всеми. Кстати, glibc - это не C++. Это даже не C, но часто реализуется на ассемблере. const в сборке нет.





Использование приведения в стиле C приведет к созданию идентичной сборки. Как видно в обозревателе компилятора:
//Source #1
int transfer(int handle, int direction, unsigned char *data, int length);
int input(int handle, void* data, int length)
{
return transfer(handle, 1, static_cast<unsigned char*>(data), length);
}
int output(int handle, const void* data, int length)
{
return transfer(handle, 0, static_cast<unsigned char*>(const_cast<void*>(data)), length);
}
//Source #2
int transfer(int handle, int direction, unsigned char *data, int length);
int input(int handle, void* data, int length)
{
return transfer(handle, 1, (unsigned char*)data, length);
}
int output(int handle, const void* data, int length)
{
return transfer(handle, 0, (unsigned char*)data, length);
}
//Assembly (both)
input(int, void*, int):
mov ecx, edx
mov rdx, rsi
mov esi, 1
jmp transfer(int, int, unsigned char*, int)
output(int, void const*, int):
mov ecx, edx
mov rdx, rsi
xor esi, esi
jmp transfer(int, int, unsigned char*, int)
Итак, ясно, что простое использование приведений в стиле C решит вашу проблему.
Причина многословности приведений C++ заключается в том, чтобы убедиться, что вы не делаете ошибок. Когда сопровождающий видит ваш код, важно, чтобы он видел const_cast и static_cast, потому что написание кода таким образом информирует читателя о том, что отказ от const-сущности указателя является преднамеренным и желаемым поведением. Сопровождающий кода должен видеть эти преобразования и предполагать, что за кодом скрывается намерение, вместо того, чтобы гадать, знали ли вы, что преобразование напрямую из const void* в unsigned char* может повлечь за собой риск неопределенного поведения. Ваш пример может не содержать UB (поскольку вы указали, что контракт transfer должен обрабатывать data как доступный только для чтения, когда direction равен 0), но важно, чтобы любой, кому нужно внести изменения в ваш код, понимал осознанность ваших методов кодирования .
@PaulSanders "Четче" и "Короче" не являются синонимами в этом контексте. См. этот ответ для более полного обоснования настаивания на приведении типов в стиле C++, а не на приведении типов в стиле C.
Не думаю, что я утверждал, что это так. И хотя это хороший ответ, на который вы ссылаетесь, я не считаю, что он имеет отношение к конкретному вопросу OP (потому что он просто указывает указатели на примитивные объекты).
Вы же не хотите, чтобы data был неконстантным. Что, если transfer попытался его модифицировать?
Было бы безопаснее иметь локальную копию:
#include <cstring>
int output(int handle, const void* data, int length)
{
unsigned char localData[length];
std::memcpy(localData, data, length);
return transfer(handle, 0, localData, length);
}
edit: читая комментарии, это то, что предложил Шлублу ...
Этот ответ не по теме, вопрос в том, «есть ли способ выполнить преобразование из const void * в unsigned char * за один шаг?». Вы отвечаете на вопрос "в этом сценарии правильно отбросить const?"
Кстати, в отбрасывании const здесь нет абсолютно ничего плохого: transfer не будет изменять переданный буфер, если direction равен 0.
@ Wizard79 SO полон проблем с XY. Очень часто правильный ответ на вопрос - это не то, что задал ОП.
Уродливая последовательность каста - это особенность здесь, это опасно.