2

この署名を持つテンプレートメンバー関数があります:

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_castfrom void (*)(std::__1::basic_string<char> *)to dispatch_function_t(aka void (*)(void *))は許可されていません。

dispatch_function_tはCタイプで、と同じvoid (*)(void*)です。


十分にはっきりしていたかどうかはわかりません。指定されたコールバック関数をdispatch_sync_f呼び出し、指定されたコンテキストパラメータをそのコールバック関数に渡します。(これは別のスレッドで行われますが、この質問の範囲外です。)

4

5 に答える 5

4

キャストworkするだけdispatch_function_tでなく、ポインタを使って呼び出すのでdispatch_function_tはないでしょうか。このようなキャスト自体は標準に従って有効ですが、キャストされたポインタでできることは、元のタイプにキャストすることだけです。それでも、あなたのアプローチはほとんどのコンパイラとプラットフォームで機能するはずです。より標準に準拠するように実装したい場合は、ラッパーを作成して次のようcontextwork機能させることができます。


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>);
}

于 2011-12-30T16:42:28.687 に答える
4

これがサポートされていない理由static_castは、潜在的に安全ではないためです。astd::string*は暗黙的にに変換されますがvoid*、2つは同じものではありません。正しい解決策は、関数に単純なラッパークラスを提供することです。これは、を取り、それをvoid*目的static_castの型に戻し、このラッパー関数のアドレスを関数に渡します。(実際には、最近のマシンでは、reinterpret_castデータへのすべてのポインターが同じサイズと形式であるため、を回避できます。このように角を切りたいかどうかはあなた次第ですが、正当化される場合もあります。I簡単な回避策を考えると、これがそれらの1つであるとは確信していません。)

編集:もう1つのポイント:あなたはそれdispatch_function_tがCタイプであると言います。この場合、おそらく実際のタイプであり、リンケージextern "C" void (*)(void*)を持つ関数でのみ初期化できます。"C"(繰り返しになりますが、あなたはそれをうまくやる可能性がありますが、私は呼び出し規約がとで異なるコンパイラを使用しました"C""C++"

于 2011-12-30T16:35:59.393 に答える
1

私はこれがうまくいくとは信じられないか、あなたは「これはうまくいく」というかなり狭い定義を持っています(例えば、あなたはそれがあなたがすべきだと思うことをするように見える特定のセットアップを見つけました)。何をするのかはわかりdispatch_sync_f()ませんが、ローカル変数へのポインタをパラメータとして取得するのは疑わしいと思いcontextます。この変数がこのポインターの使用よりも長持ちすると仮定すると、ほとんどのプラットフォームでは得られないが、いくつかのプラットフォームでは得られる微妙な問題がまだあります。

CとC++の呼び出し規約は異なる場合があります。つまり、C ++関数へのポインターをC関数へのポインターにキャストして、これが呼び出し可能になることを期待することはできません。この問題の修正(および元の質問)は、もちろん、追加レベルの間接参照です。引数として取得した関数にディスパッチするのではなく、C関数(つまり、として宣言されたC ++関数extern "C")にディスパッチします。元のコンテキストと元の関数の両方を保持し、元の関数を呼び出す独自のコンテキスト。必要な[明示的な]キャストは、static_cast<>()から内部コンテキストへのポインタを復元することだけvoid*です。

テンプレートを実装しているように見えるので、このタイプを取り除くために別の間接参照を使用する必要があるかもしれません。関数テンプレートを宣言できるとは限りませんextern "C"。したがって、何らかの方法で元の型を復元する必要があります。たとえば、基本クラスと仮想関数を使用するか、std::function<void()>この変換を実行する簡単に呼び出し可能な関数オブジェクトを保持するようなものを使用します(このオブジェクトへのポインタがコンテキストになります)。

于 2011-12-30T16:44:39.667 に答える
0

reinterpret_castT *タイプからへの変換およびその逆の変換時に機能することが保証されていvoid *ます。ただし、Tの基本クラスまたは派生クラスへのポインタからまたはポインタへのポインタをキャストすることはできません。

この場合、タイプはであるwork必要がありdispatch_function_t、その関数の最初のビジネスの順序は、からvoid *へのキャストである必要がありますT *。別の引数型を使用して引数を暗黙的にキャストし、関数型をキャストすることは許可されていません。

void *理論的根拠:この標準では、すべてのポインタータイプを変換および逆変換できる限り、さまざまなタイプに対してさまざまなポインター表現が許可されているためvoid *、「最も正確な」ポインタータイプも許可されます。uint32_t *準拠する実装では、 if sizeof(uint32_t) > sizeof(char)(ie )の下位ビットをクリアしsizeof(uint32_t) > 1たり、マシン命令がこれらのポインターをより効果的に利用できる場合はポインター値をシフトしたりすることができます。タグ付きまたはシフトされたポインタ値を持つマシンでは、reinterpret_castこれは必ずしもノーオペレーションではなく、明示的に記述する必要があります。

于 2011-12-30T16:37:41.327 に答える
0

これら 2 つの関数ポインター型との間のキャストは問題ないと思います。

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

問題は、キャストしたポインターを実際に使用できないことです。元の型にキャストすることのみが有効です (これらのキャストはreinterpret_castです。これらは無関係な型であるためです)。あなたのコードから、実際のコールバック関数がどのように定義されているかわかりません。a をキャストするのではなく、dispatch_function_tのパラメーターとして受け入れることができないのはなぜですか?queue::sync

于 2011-12-30T16:34:19.760 に答える