クラスメンバ関数をCreateThreadで実行させるには?

解決


CC2008  2010-01-19 21:17:43  No: 71292  IP: [192.*.*.*]

staticでないprivateメンバ関数をCreateThreadなどで実行させるにはどうしたらよいのでしょうか?
privateでなければstaticなメンバ関数をあいだに挟めばよいと思うのですが、privateだと上手くいきません。

編集 削除
επιστημη  URL  2010-01-19 21:55:25  No: 71293  IP: [192.*.*.*]

- そのクラスのメソッド内でCreateThreadする。
- Threadに必要な引数をstructにまとめ、その中にthisも入れておき、
  friendしておく。

編集 削除
仲澤@失業者  2010-01-20 09:41:08  No: 71294  IP: [192.*.*.*]

おこがましいですが、やや補足

-スレッド関数内では渡されたthis(つまりクラスのインスタンス)
  のメンバ関数(staticでないprivate関数)を呼べます。

スレッド関数をfriendにしてますからね。

編集 削除
επιστημη  URL  2010-01-20 10:38:56  No: 71295  IP: [192.*.*.*]

↓ friendせんでもこれならおっけぃ。

#include <iostream>
#include <windows.h>

struct FactorialArgs;

class Factorial {
private:
  int result_;
  // スレッド本体
  void execute(int n) {
    int f = 1;
    for ( int i = 1; i < n; ++i ) {
      f *= i;
    }
    result_ = f;
  }
public:
  FactorialArgs* make_args(int n);
  static void run(void* v);
  int result() const { return result_; }
};

// スレッドに引き渡す引数
struct FactorialArgs {
  int n;
  Factorial* instance;
};

// ↑を作る
FactorialArgs* Factorial::make_args(int n) {
  FactorialArgs* result = new FactorialArgs();
  result->n = n;
  result->instance = this;
  return result;
}

// スレッド・エントリ 
// こっから private な Factorial::execute を呼ぶ
void Factorial::run(void* v) {
  FactorialArgs& args = *static_cast<FactorialArgs*>(v);
  args.instance->execute(args.n);
}

int main() {
  Factorial fact0;
  Factorial fact1;
  FactorialArgs* args0 = fact0.make_args(5);
  FactorialArgs* args1 = fact1.make_args(7);
  HANDLE handle[] = {
    CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(&Factorial::run), args0, 0, NULL),
    CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(&Factorial::run), args1, 0, NULL),
  };
  WaitForMultipleObjects(2, handle, TRUE, INFINITE);
  std::cout << "5! = " << fact0.result() << std::endl;
  std::cout << "7! = " << fact1.result() << std::endl;
  delete args0;
  delete args1;
}

編集 削除
tetrapod  2010-01-20 11:31:38  No: 71296  IP: [192.*.*.*]

うーん、そのコードはたまたま偶然期待通りに動いているだけかと。
__stdcall (THREAD_START_ROUTINE) 規約と
__cdecl (static メンバ関数) 規約と
では、生成されるバイナリレベルでのコール・リターン手順が違うのでうまくない。
static void run(void*); を素直に THREAD_START_ROUTINE に適合するよう
static DWORD __stdcall run(void*); に修正して
不必要な reinterpret_cast は除去し
run の最後に return 0; でも追加しておくほうがよいと思う。

編集 削除
επιστημη  URL  2010-01-20 11:41:51  No: 71297  IP: [192.*.*.*]

> コール・リターン手順が違うのでうまくない。

あー。んじゃもっぺん。

#include <iostream>
#include <windows.h>

struct FactorialArgs;

class Factorial {
private:
  int result_;
  // スレッド本体
  void execute(int n) {
    int f = 1;
    for ( int i = 1; i < n; ++i ) {
      f *= i;
    }
    result_ = f;
  }
public:
  FactorialArgs* make_args(int n);
  static DWORD __stdcall run(void* v);
  int result() const { return result_; }
};

// スレッドに引き渡す引数
struct FactorialArgs {
  int n;
  Factorial* instance;
};

// ↑を作る
FactorialArgs* Factorial::make_args(int n) {
  FactorialArgs* result = new FactorialArgs();
  result->n = n;
  result->instance = this;
  return result;
}

// スレッド・エントリ 
// こっから private な Factorial::execute を呼ぶ
DWORD __stdcall Factorial::run(void* v) {
  FactorialArgs& args = *static_cast<FactorialArgs*>(v);
  args.instance->execute(args.n);
  return 0;
}

int main() {
  Factorial fact0;
  Factorial fact1;
  FactorialArgs* args0 = fact0.make_args(5);
  FactorialArgs* args1 = fact1.make_args(7);
  HANDLE handle[] = {
    CreateThread(NULL, 0, &Factorial::run, args0, 0, NULL),
    CreateThread(NULL, 0, &Factorial::run, args1, 0, NULL),
  };
  WaitForMultipleObjects(2, handle, TRUE, INFINITE);
  std::cout << "5! = " << fact0.result() << std::endl;
  std::cout << "7! = " << fact1.result() << std::endl;
  delete args0;
  delete args1;
}

編集 削除
仲澤@失業者  2010-01-20 11:57:49  No: 71298  IP: [192.*.*.*]

ほんとにCreateThread()で良いのだろうかという
心配もありますよね(vv;)。
例のCランタイムとMFCが利用できる、できないうんぬんという。

編集 削除
tetrapod  2010-01-20 12:23:42  No: 71299  IP: [192.*.*.*]

あい。俺は CreateThread をそのまま使ったことはないっす。
後輩君にも常に _beginthread(ex) または AfxBeginThread を使うように指導しているです。

以下は蛇足
x86 は __stdcall, __cdecl, __thiscall が皆違うんだけど
x64 は実質 __fastcall 一択になったらしい。
x64 の setjmp/longjmp はアンワインドになるらしい・・・えらい違いだ。

編集 削除
CC2008  2010-01-22 22:28:45  No: 71300  IP: [192.*.*.*]

皆さん、どうもありがとうございました。
とりあえず、friendを使ってみることにしました。

マルチスレッドは、正直難しいです……

編集 削除