在C++标准库中,std::thread 和 std::ifstream 都设计为不可复制的(non-copyable),但可移动的(movable)。这样做的原因和它们的实现方式以及移动操作的优点和使用场合如下:
原因
- 不可复制的理由:
- std::thread:一个线程对象表示一个实际的操作系统线程,复制一个线程对象意味着复制一个正在运行的线程,这在逻辑上是不可行的,因为一个线程不能同时在两个地方运行。
- std::ifstream:一个文件流对象通常与一个特定的文件描述符相关联,复制文件流对象会导致多个对象共享同一个文件描述符,这会导致不一致的文件状态和未定义的行为。
- 可移动的理由:
- 允许资源的转移:通过移动操作,可以将线程或文件流对象所控制的资源从一个对象转移到另一个对象,而不会出现共享资源的情况。这保证了资源的唯一性和一致性。
实现
std::thread 和 std::ifstream 的不可复制和可移动特性是通过以下方式实现的:
不可复制:
通过将复制构造函数和复制赋值操作符声明为 delete 或 private,防止对象的复制。
class thread {
public:
thread(const thread&) = delete;
thread& operator=(const thread&) = delete;
};
可移动:
通过定义移动构造函数和移动赋值操作符,允许对象的资源在对象之间转移。
class thread {
public:
thread(thread&& other) noexcept {
// 转移资源
}
thread& operator=(thread&& other) noexcept {
if (this != &other) {
// 释放当前资源,转移其他对象的资源
}
return *this;
}
};
优点和使用场合
- 线程资源的唯一性:
- 通过移动操作,可以确保线程资源在任何时刻只被一个对象控制,避免了资源共享带来的潜在问题。
- 示例:在一个函数内部创建一个线程,并将其移动到函数外部的一个对象中:
std::thread create_thread() { std::thread t([]() { // 线程函数 }); return t; // 移动构造函数被调用 } std::thread outer_thread = create_thread(); // 移动赋值操作符被调用
- 灵活性和性能:
- 移动操作允许对象在函数调用、返回值等场景中更灵活地传递,而不需要进行资源复制,这在性能敏感的场景中尤为重要。
- 示例:在多线程编程中,传递线程对象时不需要复制线程的内部状态,减少了开销。
- 处理所有权转移:
- 在某些设计模式中,移动操作可以用于明确地转移资源的所有权,确保资源的完整性。
- 示例:在工厂模式中,生成的线程对象可以通过移动操作转移给其他组件进行管理。
总结
std::thread 和 std::ifstream 支持移动而不支持复制的理由是为了保证资源的唯一性和减少潜在的资源共享问题。通过移动构造函数和移动赋值操作符,这些对象可以在不复制底层资源的情况下进行转移,这提高了系统的灵活性、性能和安全性。在多线程编程和文件处理等场景中,这种设计模式尤为重要,确保了资源的安全管理和高效使用。
线程移动后,原线程所拥有的资源也一并转移了,原线程“只剩空壳”,析构了
当你移动一个 std::thread 对象时,它的底层线程资源会被转移给新的对象,而原来的 std::thread 对象将不再拥有任何资源,变成一个“空壳”。在C++中,这种情况下原线程对象的析构是安全的,不会导致任何问题。
线程移动的实现细节
1. 移动构造函数
class thread {
public:
thread(thread&& other) noexcept {
// 转移资源
native_handle_ = other.native_handle_;
other.native_handle_ = nullptr; // 原对象的资源指针置空
}
// 其他成员函数和数据成员
private:
std::thread::native_handle_type native_handle_;
};
在这个例子中,native_handle_ 是一个表示底层线程资源的指针。通过移动构造函数,原线程对象的 native_handle_ 被赋值给新对象,而原对象的 native_handle_ 被置为 nullptr,表示它不再控制任何线程资源。
2. 移动赋值操作符
thread& operator=(thread&& other) noexcept {
if (this != &other) {
// 释放当前资源
if (joinable()) {
join(); // 或者 detach(),取决于设计
}
// 转移其他对象的资源
native_handle_ = other.native_handle_;
other.native_handle_ = nullptr;
}
return *this;
}
在移动赋值操作符中,首先确保当前对象不是自身,然后释放当前对象可能持有的资源(如果已经有一个线程在运行),最后将原对象的资源指针转移给当前对象,并将原对象的资源指针赋空。