sun_chuan 发表于 2024-7-19 13:21:24

c++构造函数,静态成员和多线程问题求助

最近学习c++多线程时候碰到的问题:
#include <iostream>
#include<thread>
#include<mutex>
#include<Windows.h>

static std::once_flag once;
class Log
{
public:
    //Log() {};//(问题一)我发现这里为啥非要加一个构造函数?不加构造函数就会提示“类 "Log" 不存在默认构造函数”
    Log(const Log& log) = delete;
    Log& operator=(const Log& log) = delete;

    static Log& GetInstance()
    {
      Log* log = NULL;
      std::call_once(once, init, log);
      return *log;
    }

    static void init(Log* log)
    {
      if (log == NULL)log = new Log;

    }

    void PrintLog(std::string msg)
    {
      std::cout << __TIME__ << "" << msg << '\n';
    }

private:
    //static std::once_flag once; (问题二)为啥这个放在类外面就没事了?放在类里面就会报错“无法解析的外部符号 "private: static struct std::once_flag Log::once" (?once@Log@@0Uonce_flag@std@@A)”

};

void PrintError()
{
    Log::GetInstance().PrintLog("error\n");
}



int main()
{
    std::thread t1(PrintError);
    std::thread t2(PrintError);
    t1.join();
    t2.join();
}

FishC 发表于 2024-7-19 13:21:38

哈哈,这个问题看起来蛮有意思的,尤其是要在多线程的背景下处理构造函数和静态成员的问题。让我们一条一条地解决你的疑惑吧。

### 问题一:为什么需要一个构造函数?

首先,C++中的类如果不显式声明构造函数,编译器会默认生成一个默认的构造函数。然而,如果你在类里定义了其他构造函数(即使是拷贝构造函数、移动构造函数等),编译器就不会提供默认构造函数了。所以你的代码中:
//Log() {}; // 注释掉了默认构造函数
Log(const Log& log) = delete;
Log& operator=(const Log& log) = delete;
由于你手动删除了拷贝构造函数,因此编译器不会再为你生成默认构造函数。这时你就必须自己声明一个默认的构造函数(尽管它啥都不干)。
Log() {};// 这个构造函数还是要有的呀!

### 问题二:静态变量和多线程

你的 `Log` 类是个经典的单例模式实现方法,用来确保多线程情况下只有一个 `Log` 实例。根据你的代码:

static Log& GetInstance()
{
    Log* log = NULL;
    std::call_once(once, init, log);
    return *log;
}

static void init(Log* log)
{
    if (log == NULL) log = new Log;
}

这里的问题在于:`log` 是局部变量,导致即使在 `init` 函数里 `log` 变成了指向 `new Log` 的指针,也不会回传给 `GetInstance` 里面的 `log`。

但其实这个问题可以通过以下改进来解决:
static Log& GetInstance()
{
    static Log* log = NULL;// 将log变成静态的
    std::call_once(once, init, log);
    return *log;
}

static void init(Log*& log)
{
    if (log == NULL) log = new Log;
}

这样一来,`log` 变成了 `GetInstance` 函数内的静态变量,并且在 `init` 函数参数里使用引用来保证修改后的值能传递回来。

### 小幽默时刻

哎呀,C++真的很像养宠物——静态成员就像我们的猫,它们喜欢独处,所有人共享一份。而多线程就像试图同时给猫喂饭的一大群人,得小心别被抓,确保大家协调好了才行!所以,使用 `std::call_once` 就像给这群人派了个小队长,确保他们不会手忙脚乱,猫也不会炸毛啦~

希望这个解释对你有帮助!如果还有什么其他疑问,请随时提问,我会继续为你解答哒!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

sun_chuan 发表于 2024-7-19 13:27:07

FishC 发表于 2024-7-19 13:21
哈哈,这个问题看起来蛮有意思的,尤其是要在多线程的背景下处理构造函数和静态成员的问题。让我们一条一条 ...

为什么    static std::once_flag once;放在类里面就报错"private: static struct std::once_flag Log::once" (?once@Log@@0Uonce_flag@std@@A),必须放在类外面?

FishC 发表于 2024-7-19 13:27:20

sun_chuan 发表于 2024-07-19 13:27
为什么    static std::once_flag once;放在类里面就 ...

哈哈,C++的细节真的是一波接一波呀!让我们揭开这个谜底吧~

### 问题解析

事实上,这是因为在C++中静态成员变量的初始化规则。静态成员变量必须在类定义外面进行初始化。这是C++语言的一个规定,因为静态成员变量在所有实例中是共享的,它们并不随每个对象的创建而重新分配存储空间,而是从属于整个类的。因此,我们要在类外对它进行定义和初始化。简单来说,你需要告诉编译器,这个静态变量是在类外部定义的。

### 解决方案

你可以这样来解决这个问题:

1. 在类内部声明静态成员变量。
2. 在类外部定义并初始化静态成员变量。

下面是改进后的例子:

#include <iostream>
#include <mutex>

class Log
{
public:
    // 默认构造函数
    Log() {}

    // 禁用拷贝构造函数和赋值运算符
    Log(const Log& log) = delete;
    Log& operator=(const Log& log) = delete;

    static Log& GetInstance()
    {
      std::call_once(once, &Log::init); // 注意这里的改变
      return *log;
    }

    void PrintLog(std::string msg)
    {
      std::cout << msg << std::endl;
    }

private:
    static void init()
    {
      log = new Log;
    }

    static Log* log;
    static std::once_flag once;
};

// 在类外部定义并初始化静态成员变量
Log* Log::log = nullptr;
std::once_flag Log::once;

int main()
{
    Log& logger = Log::GetInstance();
    logger.PrintLog("Hello from FishC!");
    return 0;
}

### 为什么这样做?

- **声明 vs 定义**:在类内部,你只是在声明 `std::once_flag`,告诉编译器这个类里会有一个这样的静态成员变量。但是你并没有定义它,也没有为它分配内存空间。
- **类外定义**:在类外,你才真正定义了这个静态变量,并且为它分配了内存空间和初值。

### 小幽默时刻

C++ 的这些规则就像我们做菜时的“先声明后定义”原则。你得先告诉大家“今晚吃火锅”,然后再去买菜准备食材。而不能说“我已经准备好火锅了”,但其实家里连锅还没买。这就是为啥一定要在类外定义和初始化这些静态成员变量啦!

希望这次的解释清楚明了,如果还有什么头疼的C++问题,尽管问,我会继续用通俗易懂的方式为你解答哒!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
页: [1]
查看完整版本: c++构造函数,静态成员和多线程问题求助