Вложенные static_cast и const_cast

У меня есть системный вызов, подобный следующему:

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* за один шаг?

Уродливая последовательность каста - это особенность здесь, это опасно.

Mat 09.07.2018 17:33

Единственное преобразование C++, которое может удалить const, - это const_cast, даже reinterpret_cast (который позволяет вам попробовать преобразовать практически все) не позволит вам отбросить const. Одношаговое приведение, которое будет работать, - это приведение C. Он разрешился бы в одну и ту же (или почти) серию приведений, но, похоже, это было бы одно применение. Я бы рекомендовал вести длинные серии забросов.

François Andrieux 09.07.2018 17:33

это на самом деле функция вывода const исправьте ваш?

Moia 09.07.2018 17:33

@Moia системная функция transfer довольно уродлива, но она не изменяет переданный буфер, если direction - это 0 (вывод).

lornova 09.07.2018 17:36

Что ж, читая ваши комментарии, вы должны адаптироваться к тому, что плохо спроектировано и что вы не можете изменить. Я думаю, у вас нет другого выхода, кроме как использовать приведение в стиле C или скопировать data в буфер unsigned char, который вы бы вместо этого передали в transfer(). Вы, вероятно, не захотите этого делать.

Shlublu 09.07.2018 17:57

const в const void* data существует только для того, чтобы вы не изменяли data внутри своей функции. Но если вы отбросите его, сигнатура функции может быть с таким же успехом int output(int handle, void* data, int length). Или лучше int output(int handle, unsigned char* data, int length).

JHBonarius 09.07.2018 18:30

@JHBonarius функции input и output имеют правильные параметры, они существуют для того, чтобы скрыть некрасивую и плохую сигнатуру функции transfer.

lornova 10.07.2018 11:07

@ Wizard79, но это всего лишь мираж, поскольку вы сразу же выбросили const. Внешне вас просто «обманули» тем, что есть гарантия, что переданный вами параметр не будет изменен. Как я сказал в своем ответе: что, если transfer действительно изменит значение? Тогда вы потенциально можете иметь необъяснимое повреждение данных, отладка которого может занять много времени. Я бы не стал скрывать эту подпись, переделывая все, потому что вы вводите пользователя в заблуждение. Вместо этого сделайте копию или около того, как я предлагаю в своем ответе.

JHBonarius 10.07.2018 11:48

@JHBonarius, это не мираж, это договор. Контракт соблюдается, поскольку буфер, переданный в transfer, не изменяется, если direction равен 0. Сам const не транслируется ни в какой код, это сам контракт. Я считаю, что ваше предложение сделать копию очень плохое: оно беспричинно влияет на производительность и память.

lornova 10.07.2018 12:41

@ Wizard79 Сейчас это может быть так, но что, если кто-то изменит код transfer (или какой-либо сопоставимой функции в аналогичной ситуации). Например, теперь переупорядочивание данных в data для оптимальной связи: тогда у вас внезапно есть изменение в ваших исходных данных. Итак, вы можете быть уверены, что поступаете правильно, но по ходу дела делаете много предположений. За свою карьеру я отлаживал много старого кода, где дизайнеры делали такие предположения, которые через x лет оказались ошибкой. const_cast может быть очень опасным.

JHBonarius 10.07.2018 13:08

@JHBonarius, если и когда transfer будет делать что-то столь же глупое, как изменение выходного буфера, output будет соответственно изменен. Между прочим, вот что происходит внутри glibc write: квалификатор const для переданного буфера автоматически отбрасывается при передаче в syscall. Вы должны доверять контракту, буфер не будет изменен внутри ядра для этого системного вызова.

lornova 10.07.2018 13:19

Даже контракты могут меняться со временем. Я видел ошибки ... В этом случае я ожидал бы, что поставщик API предоставит подходящие интерфейсы transmit и receive вместо одного кольца, чтобы управлять ими всеми. Кстати, glibc - это не C++. Это даже не C, но часто реализуется на ассемблере. const в сборке нет.

JHBonarius 10.07.2018 13:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
12
316
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Использование приведения в стиле 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.

Причина многословности приведений C++ заключается в том, чтобы убедиться, что вы не делаете ошибок. Когда сопровождающий видит ваш код, важно, чтобы он видел const_cast и static_cast, потому что написание кода таким образом информирует читателя о том, что отказ от const-сущности указателя является преднамеренным и желаемым поведением. Сопровождающий кода должен видеть эти преобразования и предполагать, что за кодом скрывается намерение, вместо того, чтобы гадать, знали ли вы, что преобразование напрямую из const void* в unsigned char* может повлечь за собой риск неопределенного поведения. Ваш пример может не содержать UB (поскольку вы указали, что контракт transfer должен обрабатывать data как доступный только для чтения, когда direction равен 0), но важно, чтобы любой, кому нужно внести изменения в ваш код, понимал осознанность ваших методов кодирования .

Однако вам не следует использовать приведение в стиле C. Не согласен. Если то, что вы делаете с приведением типов в стиле C++, по сути то же самое, тогда код будет намного яснее (и короче), если вы просто закодируете приведение в стиле C и закончите с ним. Если человек, читающий код, так мало верит в вас, что не «доверяет» вам, тогда существует более глубокая проблема (и вы всегда можете прокомментировать код).
Paul Sanders 09.07.2018 19:04
Причина многословности приведений C++ заключается в том, чтобы убедиться, что вы не делаете ошибок. Нет, это для выразительности. Причина разные вкусы приведений C++ состоит в том, чтобы (попытаться) убедиться, что вы не делаете ошибок. (Добро пожаловать в уголок педантизма).
Paul Sanders 09.07.2018 19:05

@PaulSanders "Четче" и "Короче" не являются синонимами в этом контексте. См. этот ответ для более полного обоснования настаивания на приведении типов в стиле C++, а не на приведении типов в стиле C.

Xirema 09.07.2018 19:20

Не думаю, что я утверждал, что это так. И хотя это хороший ответ, на который вы ссылаетесь, я не считаю, что он имеет отношение к конкретному вопросу OP (потому что он просто указывает указатели на примитивные объекты).

Paul Sanders 09.07.2018 19:23

Вы же не хотите, чтобы 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?"

lornova 10.07.2018 12:44

Кстати, в отбрасывании const здесь нет абсолютно ничего плохого: transfer не будет изменять переданный буфер, если direction равен 0.

lornova 10.07.2018 12:48

@ Wizard79 SO полон проблем с XY. Очень часто правильный ответ на вопрос - это не то, что задал ОП.

JHBonarius 10.07.2018 12:52

Другие вопросы по теме