0

我有一个类可以处理与特定硬件的所有通信。硬件需要通过一个套接字更新数据,并在已知(和硬编码)多播地址上广播其 IP 以进行握手。

结果,该类产生了两个线程,一个线程处理与硬件的直接通信并根据该类的成员结构更新数据。另一个线程持续监控组播地址,看IP是否发生了变化。

两个线程都作为类内部的静态成员函数实现。但是,为了使用 pthread,我的理解是它们需要声明为 extern "C"。也就是说,根据我的研究,不可能将类的成员函数声明为外部“C”。我从之前参与该项目的工程师那里看到的一些代码解决了这个问题的方式是线程函数实现被包围在 extern "C" 中。即在函数声明或定义中没有extern "C",而是将整个函数定义封装在extern "C" {}中。

像这样:

extern "C" {
  // function goes here
}

我有几个关于这个实现的问题,因为我希望提高对代码的理解并在多线程编程方面做得更好。

  1. 考虑到它的实现方式,外部“C”实际上会做任何事情吗?我在这里阅读了修饰符的实际作用:In C++ source, what is the effect of extern "C"? 但是在这种情况下,考虑到我们正在处理作为类的静态成员的函数这一事实,它是否真的适用?

  2. 线程成员函数似乎通过执行 reinterpret_cast 并创建指向类的指针来绕过修改类的成员变量的事实。这是标准做法吗,因为当我看到那个演员表时我的红旗被举起?

  3. 构造函数调用 pthread_create() 来启动线程。我正计划做一些事情,比如添加一个静态成员变量,它只允许一个实例在任何给定时间与硬件通信。也就是说,我正在考虑允许调用者拥有该类的多个实例,但一次只能“连接”一个。这是标准做法,还是我的思维方式有一些“hacky”?在给定时间只会连接一个硬件,因此只需要在给定时间运行一个类实例。

感谢你的帮助。此外,如果您只是要建议“老兄,请使用 Boost 线程,FTW”之类的建议,请屏住呼吸。虽然我最终会转向线程库,但我的理解是 Boost 只是在 POSIX 系统上包装了 pthread。因此,我想在使用库宠坏自己之前先了解如何使用原始线程。虽然我的主要目标是按时交付,但没有人说我在此过程中学不到任何东西。;)

编辑添加:

为了清楚起见,这里有一些描述我看到的问题的源代码。在这种情况下,我仍然不能 100% 确定 extern "C" 正在做任何事情......

在我的 .h 中:

class MyClass
{
 private:
  static void* ThreadFunc(void* args);  // extern "C" not found in declaration
  static bool instance_;
};

在我的 .cpp 中:

MyClass::MyClass() {
  if (!instance_) {
    instance_ = true;
    // spawn threads here
  } else {
    // don't spawn threads and warn user
  }
}
extern "C" {
  void *MyClass::ThreadFunc(void* args) {  //definintion wrapped in extern C
    MyClass* myclass_ptr = static_cast<MyClass*>(args);
    // ... more code
    return static_cast<void*> 0;
  }
4

2 回答 2

3

问题 #1

考虑到我们正在处理作为类的静态成员的函数,它实际上是否适用?

适用但不是必需的。符合要求的实现(编译器)知道相关的类型信息以按原样使用函数指针、执行隐式转换或因错误而失败。

请记住,在指定语言链接时可以应用其他属性。这可能包括但不限于特定的调用约定。然而,该标准将其留给实施。

从 $7.5/1

与具有语言链接的实体关联的某些属性特定于每个实现,此处不予描述。例如,特定的语言链接可能与表示具有外部链接的对象和函数的名称的特定形式相关联,或者与特定的调用约定等相关联。

通过指定语言链接,您可以让实现处理这些属性的细节。否则,您将需要使用语言扩展来指定调用约定等内容。即使您这样做,也可能存在其他未知或无法通过特定于实现的扩展应用的属性。

语言链接也会影响实体的类型,在本例中是函数。

从 $7.5/1

具有不同语言链接的两个函数类型是不同的类型,即使它们在其他方面相同。

在您的示例中,指向静态成员函数的指针可转换为线程启动函数的函数指针类型。这就是为什么您的代码在没有指定语言链接和没有显式转换的情况下正常工作的原因。

这并不意味着您不应该指定语言链接。如果您担心实现之间的可移植性,那么使用它是一个好主意。例如,一些实现fastcall用作默认的 C 调用约定,而其他实现cdecl用作默认。在没有语言链接说明符的情况下,代码将无法编译,至少无法使用符合要求的实现。

下面的示例演示了如果使用不同的调用约定,转换将如何失败。我选择使用调用约定,因为它们在示例中最容易表达。

#include <iostream>

#if defined(__GNUC__)
#define FASTCALL __attribute__((fastcall))
#elif defined(_MSC_VER)
#define FASTCALL __fastcall
#endif

extern "C" 
{
    typedef void (*FUNC1)(); // Default C calling convention
    typedef void (FASTCALL *FUNC2)();  // fastcall calling convention
    void do1(FUNC1 f) { f(); }
    void do2(FUNC2 f) { f(); }
}

struct X
{
    static void test() {}
};

int main()
{
    do1(X::test);   // Ok. Implicit conversion available
    do2(X::test);   // Fail! Incompatible types. No conversion available
}

问题2

...通过执行 a并创建指向类的指针来修改类的成员变量。reinterpret_cast这是标准做法吗,因为当我看到那个演员表时我的红旗被举起?

使用强制转换是实现此目的的唯一方法,但reinterpret_cast没有必要使用,IMO 绝对应该避免。传递给的线程启动函数pthread_createvoid指针作为它的唯一参数。static_cast就足够了,而且在行为上要窄得多。

问题 #3

我正计划做一些事情,比如添加一个静态成员变量......

这个问题是主观的,你如何解决这个问题完全取决于你。如果没有一些代码,很难判断您的实现是否是 hack。

最后一点……伙计!使用 Boost 哟!:)

于 2013-06-15T03:31:35.990 回答
2

You can't declare a static member function extern C, but you can call a static member function from another function that is extern C. In the cpp file where your thread function is defined:

namespace {
extern "C" {
void* some_thread_func(void* arg) {
  return MyClass::static_thread_func(arg);
}
}}

// static member
// (You can omit this "middle-man" if "do_stuff()" is public.)
void* MyClass::static_thread_func(void* arg) {
  return static_cast<MyClass*>(arg)->do_stuff();
}

void MyClass::start_thread_func() {
  pthread_t thread;
  int error = pthread_create(&thread, NULL, some_thread_func, this);
  // handle errors, store "thread" somewhere appropriate.
  ...
}

The multiple instances / one connected question is a design issue and I don't know what design really fits the rest of your program - but I will say that Singleton seems to reflect your design requirement that there is a single device to manage.

Finally, don't use boost::thread, use std::thread ;)

于 2013-06-15T04:13:12.883 回答