Как я могу избавиться от этого reinterpret_cast, или это использование в порядке? - PullRequest
2 голосов
/ 30 декабря 2011

У меня есть функция-член шаблона с такой подписью:

template<typename T> void sync(void (*work)(T*), T context);

Может вызываться с указателем на функцию, которая принимает аргумент типа T*. context передается этой функции. Реализация такая:

template<typename T> void queue::sync(void (*work)(T*), T context) {
  dispatch_sync_f(_c_queue, static_cast<void*>(&context),
                  reinterpret_cast<dispatch_function_t>(work));
}

Использует reinterpret_cast<> и работает. Проблема в том, что стандарт не определяет это очень хорошо, и это очень опасно. Как я могу избавиться от этого? Я попытался static_cast, но это дало мне ошибку компилятора:

static_cast с void (*)(std::__1::basic_string<char> *) до dispatch_function_t (он же void (*)(void *)) не допускается.

dispatch_function_t относится к типу C и совпадает с void (*)(void*).


Я не уверен, что был достаточно ясен. dispatch_sync_f делает то, что вызывает данную функцию обратного вызова и передает заданный параметр контекста этой функции обратного вызова. (Это делается в другом потоке, хотя это выходит за рамки этого вопроса.)

Ответы [ 5 ]

4 голосов
/ 30 декабря 2011

Полагаю, вы не только приводите work к dispatch_function_t, но и вызываете его через указатель dispatch_function_t, не так ли?Такое приведение действительно в соответствии со стандартом, но все, что вы можете сделать с приведенным указателем, приведено обратно к исходному типу.Тем не менее, ваш подход должен работать с большинством компиляторов и платформ.Если вы хотите реализовать его так, чтобы оно соответствовало стандартам, вы можете создать оболочку для функций context и work следующим образом:


template <typename T>
struct meta_context_t
{
  T *context;
  void (*work)(T*);
};

template <typename T>
void thunk(void *context)
{
  meta_context_t<T> *meta_context = static_cast<meta_context_t<T> *>(context);
  meta_context->work(meta_context->context);
}

template<typename T> void queue::sync(void (*work)(T*), T context) {
  meta_context_t<T> meta_context =
  {
    &context,
    work
  };

  dispatch_sync_f(_c_queue, static_cast<void*>(&meta_context),
                thunk<T>);
}

4 голосов
/ 30 декабря 2011

Причина, по которой static_cast не поддерживается, заключается в том, что это потенциально небезопасно.В то время как std::string* неявно преобразуется в void*, эти два значения не одно и то же.Правильное решение состоит в том, чтобы предоставить простой класс-оболочку для вашей функции, который принимает void* и static_cast s возвращает его до нужного типа, и передает адрес этой функции-оболочки вашей функции.(На практике на современных машинах вам все равно с reinterpret_cast, так как все указатели на данные имеют одинаковый размер и формат. Хотите ли вы обрезать углы, как это зависит от вас, но есть случаев, когда это оправдано. Я просто не уверен, что это один из них, учитывая простой обходной путь.)

РЕДАКТИРОВАТЬ: Еще один момент: вы говорите, что dispatch_function_t является Cтип.Если это так, то действительный тип, вероятно, extern "C" void (*)(void*), и вы можете инициализировать его только с функциями, имеющими связь "C".(Опять же, вам, вероятно, это сойдет с рук, но я использовал компиляторы, где соглашения о вызовах были разными для "C" и "C++".)

1 голос
/ 30 декабря 2011

Я не могу поверить, что это работает, или у вас есть довольно узкое определение «это работает» (например, вы нашли одну конкретную установку, где, кажется, она делает то, что, как вы думаете, должна делать). Мне не ясно, что делает dispatch_sync_f(), но я думаю, что это подозрительно, что он получает указатель на локальную переменную context в качестве параметра. Предполагая, что эта переменная переживает использование этого указателя, по-прежнему существует тонкая проблема, которая не поможет вам на большинстве платформ, но на некоторых:

Соглашения о вызовах C и C ++ могут отличаться. То есть вы не можете привести указатель на функцию C ++ к указателю на функцию C и надеяться, что это будет вызвано. Решение этой проблемы - и ваш первоначальный вопрос - это, конечно, дополнительный уровень косвенности: не отправляйте функцию, которую вы получаете в качестве аргумента, а скорее отправляйте ее функции C (т.е. функции C ++, объявленной как extern "C" ), который берет свой собственный контекст, содержащий как исходный контекст, так и исходную функцию, и вызывает исходную функцию. Единственное необходимое явное приведение - это static_cast<>(), восстанавливающий указатель на ваш внутренний контекст из void*.

Поскольку вы, кажется, реализуете шаблон, вам может потребоваться использовать другое косвенное обращение, чтобы избавиться от этого типа: я не думаю, что шаблоны функций могут быть объявлены extern "C". Так что вам нужно как-то восстановить исходный тип, например, используя базовый класс и виртуальную функцию или что-то вроде std::function<void()>, содержащее легко вызываемый функциональный объект, выполняющий это преобразование (указатель на этот объект будет вашим контекстом).

0 голосов
/ 30 декабря 2011

reinterpret_cast гарантированно работает при конвертации из типа T * в void * и обратно.Однако недопустимо приводить от или к указателю на базовый или производный класс T.

Тип work в этом случае должен быть dispatch_function_t, а первый порядокбизнес в этой функции должен быть приведен от void * до T *.Неявное приведение аргумента с использованием другого типа аргумента и приведение типа функции не допускается.

Обоснование: стандарт допускает разные представления указателей для разных типов, если все типы указателей могут быть преобразованы в void *и обратно, поэтому void * является «наиболее точным» типом указателя.Соответствующая реализация позволяет очищать биты нижнего порядка uint32_t *, если sizeof(uint32_t) > sizeof(char) (то есть sizeof(uint32_t) > 1), или даже сдвигать значение указателя, если машинные инструкции могут использовать эти указатели более эффективно;на машине со значениями тегов или смещенных указателей reinterpret_cast не обязательно запрещен и должен быть записан явно.

0 голосов
/ 30 декабря 2011

Я считаю, что приведение к / из этих двух типов указателей на функции в порядке:

void(*)(void*)
void(*)(T*)

Проблема в том, что вы не можете использовать указатель, который вы так навели. Допустимо только приведение к исходному типу (и это приведение reinterpret_cast, потому что это не связанные типы). Из вашего кода я не вижу, как определяется ваша реальная функция обратного вызова. Почему вы не можете принять dispatch_function_t в качестве параметра для queue::sync вместо его приведения?

...