首页 > 后端开发 > C++ > 正文

C++中如何安全地传递对象所有权 移动语义与智能指针结合使用

P粉602998670
发布: 2025-08-12 13:43:01
原创
141人浏览过

c++++中安全传递对象所有权需使用移动语义和智能指针。1. 移动语义通过右值引用和std::move实现资源转移,避免深拷贝并确保源对象处于有效但未指定状态;2. 智能指针管理资源生命周期,其中std::unique_ptr实现独占所有权,只能通过std::move转移所有权;3. std::shared_ptr实现共享所有权,通过引用计数自动释放资源;4. std::weak_ptr作为辅助打破循环引用,不增加引用计数,访问时需lock()提升为shared_ptr。结合二者可高效安全地管理资源流动,确保程序内存安全与健壮性。

C++中如何安全地传递对象所有权 移动语义与智能指针结合使用

在C++中安全地传递对象所有权,核心在于理解并恰当运用移动语义和智能指针。这不仅仅是语法上的选择,更是对资源生命周期管理的深思熟虑,它关乎性能、内存安全,以及代码的健壮性。说到底,就是让资源在程序中流动起来,而不是简单地复制,同时确保它们总有明确的归属,不至于无人管理或被重复释放。

C++中如何安全地传递对象所有权 移动语义与智能指针结合使用

解决方案

安全地传递C++中的对象所有权,主要依赖于两个现代C++的核心特性:移动语义(Move Semantics)和智能指针(Smart Pointers)。

立即学习C++免费学习笔记(深入)”;

C++中如何安全地传递对象所有权 移动语义与智能指针结合使用

移动语义 移动语义允许我们从一个对象“窃取”其资源(如堆内存、文件句柄等),并将其转移给另一个对象,而不是进行昂贵的深拷贝。这对于临时对象或即将销毁的对象尤其有用。当源对象被移动后,它通常处于一个有效但未指定的状态(通常是“空”),并且其析构函数不会释放已转移的资源。这通过右值引用(

&&
登录后复制
)和移动构造函数/移动赋值运算符实现,
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
是将左值转换为右值引用的关键工具,它本身不执行任何移动操作,只是告诉编译器“这个对象可以被移动了”。

智能指针 智能指针是管理动态分配内存的类模板,它们在对象生命周期结束时自动释放内存,从而避免了内存泄漏。它们的核心思想是“资源获取即初始化”(RAII),即在构造时获取资源,在析构时释放资源。

C++中如何安全地传递对象所有权 移动语义与智能指针结合使用
  1. std::unique_ptr
    登录后复制
    登录后复制
    登录后复制
    : 实现独占所有权。一个
    unique_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    实例独占管理一个对象。它不能被复制,但可以通过
    std::move
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    进行所有权转移。一旦所有权被转移,原
    unique_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    将不再拥有该对象。这是管理动态分配内存的首选,因为它开销最小,且所有权语义清晰。

  2. std::shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    : 实现共享所有权。多个
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    实例可以共同拥有同一个对象。内部通过引用计数机制来跟踪有多少个
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    指向该对象。当最后一个
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    被销毁时,它所管理的对象才会被删除。适用于多个部分需要访问并共享同一个资源的情况。

  3. std::weak_ptr
    登录后复制
    登录后复制
    登录后复制
    : 作为
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的辅助,它不拥有对象,也不会增加引用计数。它提供了一种观察
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    所管理对象的方式,而不会阻止该对象被销毁。主要用于解决
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    可能导致的循环引用问题。当需要访问
    weak_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    指向的对象时,需要先将其提升(
    lock()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    )为一个
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制

将移动语义与智能指针结合使用,可以实现高效且安全的对象所有权传递。例如,当一个函数返回一个

unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
时,编译器会自动应用移动语义,避免不必要的拷贝。当将一个
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
作为参数传递给一个函数时,如果函数需要接管所有权,可以使用
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
显式转移。对于
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,其拷贝操作本身就是所有权共享的体现,而移动操作则是在不改变引用计数的前提下,将所有权从一个
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
变量转移到另一个。

std::unique_ptr
登录后复制
登录后复制
登录后复制
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
如何确保独占所有权的安全转移?

std::unique_ptr
登录后复制
登录后复制
登录后复制
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的组合,在C++中是实现独占所有权安全转移的基石。理解这一点,我觉得是掌握现代C++资源管理的关键一步。
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的设计哲学就是“我独占,你别碰”,它天生就是不可复制的,这意味着你不能简单地用
=
登录后复制
运算符或拷贝构造函数来创建另一个指向相同资源的
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。这种设计从编译期就杜绝了“一物多主”导致重复释放的可能。

那么,当我们需要把一个

unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
所管理的对象“给”另一个
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
时,
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就派上用场了。它不是真的移动数据,它更像是一个“信号灯”,告诉编译器:“嘿,这个
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
我不打算再用了,它的资源可以安全地被别人接管。”
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
本质上是将一个左值(具名变量)强制转换为右值引用。当
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
看到一个右值引用时,它会触发其移动构造函数或移动赋值运算符。

这个移动操作是“资源窃取”的过程:新的

unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
会从旧的
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
那里“拿走”内部的裸指针,然后旧的
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
内部的裸指针会被置空(
nullptr
登录后复制
)。这样一来,在移动操作之后,原先的
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就不再拥有任何资源了,它的析构函数也不会尝试释放任何东西。新的
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
则完全接管了资源的生命周期。这种机制确保了在任何时候,只有一个
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
实例负责管理特定的资源,从而彻底避免了双重释放(double free)的问题。

#include <iostream>
#include <memory>
#include <vector>

class MyResource {
public:
    int id;
    MyResource(int i) : id(i) { std::cout << "MyResource " << id << " created.\n"; }
    ~MyResource() { std::cout << "MyResource " << id << " destroyed.\n"; }
    void do_something() { std::cout << "MyResource " << id << " doing something.\n"; }
};

// 函数接收 unique_ptr 并接管所有权
void process_resource(std::unique_ptr<MyResource> res) {
    if (res) {
        res->do_something();
        // res 在函数结束时自动销毁其管理的对象
    } else {
        std::cout << "No resource to process.\n";
    }
}

// 函数返回 unique_ptr
std::unique_ptr<MyResource> create_resource(int id) {
    return std::make_unique<MyResource>(id); // 返回时自动移动
}

int main() {
    std::cout << "--- unique_ptr ownership transfer example ---\n";

    // 1. 创建一个 unique_ptr
    std::unique_ptr<MyResource> ptr1 = std::make_unique<MyResource>(101);
    ptr1->do_something();

    // 2. 尝试拷贝,会编译错误
    // std::unique_ptr<MyResource> ptr_copy = ptr1; // 错误:unique_ptr 不能被拷贝

    // 3. 使用 std::move 转移所有权
    std::unique_ptr<MyResource> ptr2 = std::move(ptr1); // ptr1 的所有权转移给 ptr2
    if (ptr1) {
        std::cout << "ptr1 still holds resource? This should not happen.\n";
    } else {
        std::cout << "ptr1 is now empty after move.\n";
    }
    ptr2->do_something(); // ptr2 现在是资源的唯一所有者

    // 4. 将所有权传递给函数
    std::unique_ptr<MyResource> ptr3 = std::make_unique<MyResource>(102);
    process_resource(std::move(ptr3)); // ptr3 的所有权转移给函数参数 res
    if (!ptr3) {
        std::cout << "ptr3 is empty after passing to function.\n";
    }

    // 5. 从函数接收所有权
    std::unique_ptr<MyResource> ptr4 = create_resource(103);
    ptr4->do_something();

    std::cout << "--- End of unique_ptr example ---\n";
    return 0;
}
登录后复制

这段代码清晰地展示了

unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
如何通过
std::move
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
安全地转移所有权,以及原所有者如何变得“空”而不再管理资源。这是一种非常高效且安全的资源管理方式。

什么时候应该考虑使用
std::shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
进行对象所有权共享?

使用

std::shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
进行对象所有权共享,通常发生在这样一些场景:一个资源,它的生命周期需要被多个不同的、彼此独立的逻辑单元所共同管理。这不是那种简单的“传递”关系,而是“我们都用着,谁也别急着扔”的状态。我个人觉得,当你发现一个对象需要在多个地方被引用,并且这些地方都对它的存在与否负有责任时,
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就该出场了。

最典型的例子可能就是缓存系统、观察者模式或者图形渲染中的纹理/模型管理。比如,一个纹理对象可能被场景中的多个模型共享,每个模型都持有对该纹理的引用。只要有一个模型还在使用这个纹理,纹理就不能被销毁。只有当所有使用它的模型都消失了,纹理才应该被释放。这时候,

shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的引用计数机制就显得非常自然和高效了。

shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
内部维护着一个引用计数,每当它被复制时,计数就会增加;每当一个
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
实例被销毁时,计数就会减少。当引用计数降到零时,表示没有任何
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
再指向该对象了,此时
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就会自动删除所管理的对象。

当然,

shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
并非没有代价。相比
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,它会有额外的内存开销(需要存储引用计数和弱引用计数)以及运行时开销(原子操作来更新引用计数,以确保线程安全)。所以,我的经验是,如果一个资源能够明确地由一个实体独占,那就优先选择
unique_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。只有当确实存在多方共享且共同管理生命周期的需求时,才考虑
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。过度使用
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
可能导致不必要的性能损耗,甚至因为循环引用而引发内存泄漏(稍后会提到
weak_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
来解决这个问题)。

#include <iostream>
#include <memory>
#include <vector>

class DataProcessor {
public:
    int id;
    DataProcessor(int i) : id(i) { std::cout << "DataProcessor " << id << " created.\n"; }
    ~DataProcessor() { std::cout << "DataProcessor " << id << " destroyed.\n"; }
    void process() { std::cout << "DataProcessor " << id << " processing data.\n"; }
};

void consumer_a(std::shared_ptr<DataProcessor> p) {
    if (p) {
        std::cout << "Consumer A is using DataProcessor " << p->id << ". Ref count: " << p.use_count() << "\n";
        p->process();
    }
}

void consumer_b(std::shared_ptr<DataProcessor> p) {
    if (p) {
        std::cout << "Consumer B is using DataProcessor " << p->id << ". Ref count: " << p.use_count() << "\n";
        p->process();
    }
}

int main() {
    std::cout << "--- shared_ptr ownership sharing example ---\n";

    // 创建一个 shared_ptr
    std::shared_ptr<DataProcessor> shared_data = std::make_shared<DataProcessor>(201);
    std::cout << "Initial ref count: " << shared_data.use_count() << "\n";

    // 多个 shared_ptr 实例共享同一个对象
    std::shared_ptr<DataProcessor> another_ref = shared_data; // 拷贝,引用计数增加
    std::cout << "After another_ref created, ref count: " << shared_data.use_count() << "\n";

    consumer_a(shared_data); // 传递 shared_ptr,引用计数在函数内部临时增加
    std::cout << "After consumer_a, ref count: " << shared_data.use_count() << "\n";

    consumer_b(another_ref); // 再次传递,引用计数再次增加
    std::cout << "After consumer_b, ref count: " << shared_data.use_count() << "\n";

    // 当 shared_data 和 another_ref 都超出作用域时,DataProcessor 才会被销毁
    std::cout << "Exiting main scope. DataProcessor will be destroyed when all shared_ptr instances are gone.\n";

    return 0;
} // shared_data 和 another_ref 在这里销毁,引用计数降为0,DataProcessor 201 被销毁
登录后复制

这个例子展示了

shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
如何允许多个指针安全地共享同一个对象的生命周期。当
shared_data
登录后复制
another_ref
登录后复制
都离开作用域时,
DataProcessor
登录后复制
实例才会被销毁。

避免所有权陷阱:
std::weak_ptr
登录后复制
登录后复制
登录后复制
的角色与常见误区

在C++的对象所有权管理中,尤其是当你开始大量使用

std::shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
时,一个非常微妙但也非常致命的陷阱就是“循环引用”。这是我个人在早期使用
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
时踩过的一个大坑,因为它不像内存泄漏那样直接导致程序崩溃,而是默默地吞噬内存,直到系统资源耗尽。

简单来说,循环引用就是两个或更多个

shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
相互持有对方的
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,形成一个闭环。比如,A持有一个指向B的
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,同时B也持有一个指向A的
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。这样一来,即使外部已经没有其他
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
指向A或B了,它们的引用计数也永远不会降到零,因为它们彼此互相引用着。结果就是,这两个对象(以及它们管理的资源)永远不会被销毁,造成内存泄漏。

std::weak_ptr
登录后复制
登录后复制
登录后复制
就是为了打破这种循环引用而生的。它是一种“弱引用”,或者说“观察者”指针。它指向一个由
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
管理的对象,但它本身不增加对象的引用计数。这意味着
weak_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
不会阻止对象被销毁。你可以把它想象成一个“不负责任”的旁观者:它知道对象在哪儿,但它不参与对象的生命周期管理。

当你想通过

weak_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
访问对象时,你需要先调用它的
lock()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
方法。
lock()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
会尝试返回一个
std::shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。如果对象仍然存在(即还有
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在管理它),
lock()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就会成功返回一个有效的
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,此时对象的引用计数会临时增加;如果对象已经被销毁了(因为所有
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
都消失了),
lock()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就会返回一个空的
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。这种机制使得
weak_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
成为解决循环引用的完美方案:在循环的一侧,使用
weak_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
而不是
shared_ptr
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

常见误区和注意事项:

  1. 忘记
    std::move
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    unique_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    搭配
    :很多人习惯了C++98的拷贝语义,在需要转移
    unique_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    所有权时,忘记使用
    std::move
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ,导致编译错误。记住,
    unique_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    是不能被拷贝的。
  2. 滥用
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    :并非所有需要共享访问的对象都适合用
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    。如果一个对象只需要被观察(比如一个只读配置),或者其生命周期明确由某个父对象管理,那么简单的引用、原始指针(确保生命周期安全)或者
    unique_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    可能更合适。
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的开销是真实存在的。
  3. 原始指针与智能指针混用不当:将智能指针管理的对象的原始指针暴露出去,并且这个原始指针的生命周期超出了智能指针的范围,或者在智能指针管理之外通过原始指针
    delete
    登录后复制
    对象,这都是非常危险的行为。一旦智能指针发现它管理的原始指针被“偷跑”或者提前销毁,就会导致未定义行为。
  4. 不理解
    weak_ptr::lock()
    登录后复制
    的意义
    weak_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    本身不能直接访问对象,必须先
    lock()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    成功才能得到一个临时的
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    去操作。在访问前检查
    lock()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    返回的
    shared_ptr
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    是否为空是至关重要的。
  5. 过早优化或过度设计:有时候,一个简单的值传递或者引用传递,如果对象的生命周期很短,或者明确由调用者管理,反而比引入智能指针更清晰。不要为了使用智能指针而使用智能指针。
#include <iostream>
#include <memory>
#include <vector>

class B; // 前向声明

class A {
public:
    std::shared_ptr<B> b_ptr; // 可能会导致循环引用
    int id;

    A(int i) : id(i) { std::cout << "A " << id << " created.\n"; }
    ~A() { std::cout << "A " << id << " destroyed.\n"; }

    void set_b(std::shared_ptr<B> b) {
        b_ptr = b;
    }
};

class B {
public:
    std::weak_ptr<A> a_ptr; // 使用 weak_ptr 打破循环引用
    int id;

    B(int i) : id(i) { std::cout << "B " << id << " created.\n"; }
    ~B() { std::cout << "B " << id << " destroyed.\n"; }

    void set_a(std::shared_ptr<A> a) {
        a_ptr = a;
    }

    void access_a() {
        if (auto shared_a = a_ptr.lock()) { // 尝试提升为 shared_ptr
            std::cout << "B " << id << " successfully accessed A " << shared_a->id << ".\n";
        } else {
            std::cout << "B " << id << " tried to access A, but A has been destroyed.\n";
        }
    }
};

int main() {
    std::cout << "--- shared_ptr circular reference example with weak_ptr ---\n";

    { // 限制作用域,观察析构行为
        std::shared_ptr<A> my_a = std::make_shared<A>(301);
        std::shared_ptr<B> my_b = std::make_shared<B>(302);

        std::cout << "A ref count: " << my_a.use_count() << ", B ref count: " << my_b.use_count() << "\n";

        // 建立双向引用
        my_a->set_b(my_b); // A 持有 B 的 shared_ptr
        my_b->set_a(my_a); // B 持有 A 的 weak_ptr (关键!)

        std::cout << "After setting links:\n";
        std::cout << "A ref count: " << my_a.use_count() << ", B ref count: " << my_b.use_count() << "\n";
        // 此时 A 的引用计数为 1 (my_a) + 1 (my_b->a_ptr.lock() if it were shared_ptr)
        // 实际上 my_a 引用计数为 1 (my_a), my_b 引用计数为 2 (my_b, my_a->b_ptr)

        my_b->access_a(); // B 尝试访问 A

    } // my_a 和 my_b 在这里超出作用域

    std::cout << "--- End of weak_ptr example ---\n";
    // 如果 B 使用的是 shared_ptr<A> 而不是 weak_ptr<A>,
登录后复制

以上就是C++中如何安全地传递对象所有权 移动语义与智能指针结合使用的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号