wordpress网站导航菜单插件,server2012 wordpress,系统开发步骤,做公益网站需要哪些部门认证在C中#xff0c;内存泄漏是一个不太容易发现但又有一定影响的问题#xff0c;而且在引入了异常的机制之后#xff0c;代码的走向就更加不可控#xff0c;更容易发生内存泄露。【补充#xff1a;内存泄露#xff08;Memory Leak#xff09;指的是在程序运行期间#xf…在C中内存泄漏是一个不太容易发现但又有一定影响的问题而且在引入了异常的机制之后代码的走向就更加不可控更容易发生内存泄露。【补充内存泄露Memory Leak指的是在程序运行期间动态分配的内存没有被释放或无法被回收从而导致这些内存块一直被占用而无法再被使用的情况。】
比如这段代码
int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
void func()
{int* ptr new int;cout div() endl;delete ptr;
}
int main()
{try{func();}catch (exception e){cout e.what() endl;}return 0;
}当div函数抛出异常例如b 0时func函数中的ptr指针通过new分配的内存将不会被释放因为delete ptr;这一行在异常抛出后根本不会被执行。因此ptr所指向的内存块将被遗留在内存中导致内存泄露。
为了避免这种情况一个常见的做法是使用 RAIIResource Acquisition Is Initialization原则来管理资源这就引入了智能指针如std::unique_ptr来自动管理内存或者确保在异常发生时总是能够释放已分配的内存。RAII是一种利用对象生命周期来控制程序资源如内存、文件句柄、互斥量等等的简单技术即在对象构造时获取资源在对象析构的时候释放资源。
智能指针的原理分析通过对象来管理获取的资源保证在对象结束生命周期时可以自动调用析构函数避免获取的资源没有被释放。
templateclass T
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout delete: _ptr endl;delete _ptr;}private:T* _ptr;
};
int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
void func()
{SmartPtrint sp(new int);cout div() endl;
}
int main()
{try{func();}catch (exception e){cout e.what() endl;}return 0;
}1. auto_ptr
[不推荐使用] std::auto_ptr有许多设计缺陷并且已经在C11中被弃用在C17中彻底移除。
问题 不安全的所有权转移 std::auto_ptr在赋值或拷贝时会转移所有权即把内存的所有权从一个auto_ptr对象转移到另一个auto_ptr对象。这种行为很容易引起程序错误特别是在函数参数传递和容器使用时。 例如 std::auto_ptrint p1(new int(5));
std::auto_ptrint p2 p1; // p1的所有权被转移给p2
// p1变为空悬指针可能导致未定义行为这种所有权的自动转移可能在编程中引入难以发现的bug尤其是在复杂代码中。 不支持标准容器 因为std::auto_ptr的所有权转移行为不能将其放入STL容器中如std::vector、std::list等。标准容器要求其元素能够被复制而std::auto_ptr的复制语义是移动语义这违反了标准容器的要求。 不符合现代C的智能指针语义 std::auto_ptr的语义与现代C的资源管理思想RAII和明确的所有权管理不匹配。std::auto_ptr的行为不够直观容易造成混淆和错误。
2. unique_ptr
std::unique_ptr被引入来解决std::auto_ptr的问题并成为现代C的首选智能指针。
优势 明确的唯一所有权 std::unique_ptr明确表示它拥有的对象具有唯一的所有权因此不会有意外的所有权转移问题。在任何需要转移所有权的情况下都必须显式地使用std::move这样更清晰、直观避免了不必要的错误。 例如 std::unique_ptrint p1(new int(5));
// std::unique_ptrint p2(p1); // error
// p1所有权被转移给p2p1被显式地std::move
std::unique_ptrint p2 std::move(p1); 支持标准容器 std::unique_ptr的设计符合标准容器的要求可以安全地用于容器中提供更好的内存管理和性能。 更好的性能 std::unique_ptr不需要在复制时进行所有权的转移检查因此在性能上更优。 现代C标准支持 std::unique_ptr是C11标准引入的现代智能指针类型是当前及未来C代码的首选。它的设计符合现代C语言的最佳实践。
这里是使用std::unique_ptr对原先的代码进行改进
#include iostream
#include stdexcept
#include memory
using namespace std;int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}void func()
{// 使用智能指针来管理内存unique_ptrint ptr make_uniqueint();cout div() endl;// 不需要手动delete智能指针会在超出作用域时自动释放内存
}int main()
{try{func();}catch (exception e){cout e.what() endl;}return 0;
}这样即使在div函数中抛出异常也不会发生内存泄露因为std::unique_ptr会在func函数结束时自动释放内存。
总而言之
由于std::auto_ptr的所有权管理方式不直观且容易出错在现代C开发中std::unique_ptr完全替代了std::auto_ptr。std::unique_ptr提供了更好的内存管理、更安全的所有权转移语义和更高效的性能是管理动态资源时的最佳选择。
3. shared_ptr
std::shared_ptr 是 C11 引入的另一种智能指针与 std::unique_ptr 不同它允许多个指针共享同一个对象的所有权。这使得 std::shared_ptr 特别适用于需要在多个地方使用相同对象并且对象的生命周期需要由多个使用者共同管理的场景。
主要特点和用途 共享所有权 std::shared_ptr 可以被多个指针共享。每个 shared_ptr 都维护一个指向相同对象的指针并使用一个引用计数reference count来跟踪当前有多少个指针指向同一对象。当一个 shared_ptr 被拷贝或赋值时引用计数增加当一个 shared_ptr 被销毁或重置时引用计数减少。当引用计数降为零时表示没有指针再指向这个对象这时对象才会被释放。 例如 std::shared_ptrint p1 std::make_sharedint(10); // 创建一个共享指针
std::shared_ptrint p2 p1; // p1 和 p2 共享同一个对象自动管理对象生命周期 std::shared_ptr 可以有效管理动态分配的对象生命周期无需程序员手动释放内存。当最后一个 shared_ptr 指针超出作用域或被重置时所管理的对象会自动释放。 循环引用检测 尽管 std::shared_ptr 能管理对象的生命周期但它无法处理循环引用的问题两个对象相互引用对方。如果两个 std::shared_ptr 对象形成了循环引用即 A 持有 BB 持有 A则它们的引用计数永远不会为零从而导致内存泄露。 为了解决这个问题可以搭配 std::weak_ptr 使用part4。
使用场景 在多个地方共享对象 当你希望多个对象或函数共享同一个对象并且需要自动管理该对象的生命周期时std::shared_ptr 非常有用。例如一个对象在多处被使用而且这些使用者希望共享这个对象的所有权。 工厂函数或资源管理 当一个函数创建一个对象并将其返回给多个调用者时std::shared_ptr 可以保证对象在不再被任何使用者使用时自动释放。例如在工厂函数中返回一个对象的指针多个调用者可以共享它 std::shared_ptrMyClass createObject() {return std::make_sharedMyClass();
}多线程环境下的资源共享 std::shared_ptr 可以安全地在多线程环境中共享对象因为其引用计数是线程安全的。在多线程环境中如果多个线程需要访问共享资源可以使用 std::shared_ptr 管理该资源。
与 unique_ptr 的对比 std::unique_ptr用于表示唯一所有权即对象只能由一个指针拥有当指针超出作用域时内存会自动释放。它通常用于不需要共享所有权的情况并且具有更好的性能因为它没有引用计数的开销。 std::shared_ptr用于表示共享所有权即多个指针可以共同拥有一个对象。适用于需要多个对象或函数共享同一资源的场景但它有引用计数的开销因此在性能上稍逊于 std::unique_ptr。
示例
#include iostream
#include memoryclass Example {
public:Example() { std::cout Example Constructor\n; }~Example() { std::cout Example Destructor\n; }void sayHello() const { std::cout Hello, Shared Pointer!\n; }
};int main() {std::shared_ptrExample ptr1 std::make_sharedExample(); // 引用计数为 1{std::shared_ptrExample ptr2 ptr1; // 引用计数为 2ptr2-sayHello();} // ptr2 超出作用域引用计数减为 1ptr1-sayHello();// 当 main 结束时ptr1 超出作用域引用计数为 0Example 对象被销毁return 0;
}在上面的代码中Example 对象在 ptr1 和 ptr2 之间共享。当 ptr2 超出其作用域时ptr1 仍然指向 Example 对象。当 ptr1 也超出作用域后Example 对象才被销毁。
std::shared_ptr 提供了共享对象所有权的机制适用于需要多个实体共享同一对象并自动管理对象生命周期的场景。尽管它有一定的性能开销由于引用计数但在需要共享资源的复杂系统中它是非常有用且必不可少的工具。
引用计数问题
std::shared_ptr 将引用计数reference count放在堆上而不是使用静态成员变量这是为了正确管理对象的生命周期并确保其线程安全性和多样性使用场景。以下是详细的原因
a. 支持多个实例指向不同对象
如果引用计数是静态成员变量那么所有的 std::shared_ptr 对象都将共享同一个引用计数。这意味着无论多少个 shared_ptr 指向多少个不同的对象它们都会共享相同的计数这显然是不合理的。
例如
std::shared_ptrint p1 std::make_sharedint(10);
std::shared_ptrint p2 std::make_sharedint(20);如果引用计数是静态的那么p1和p2将共享同一个引用计数这样无法区分两个不同的对象10和20的生命周期。每个shared_ptr应该独立维护其指向的对象的引用计数。
b. 确保线程安全
std::shared_ptr是线程安全的其引用计数是原子操作。将引用计数放在堆上一个独立的内存块中可以保证多个shared_ptr实例在不同线程中操作同一个对象时能够安全地增加或减少计数。
如果引用计数是静态成员变量那么对引用计数的所有操作都会涉及同一个变量会导致多个线程同时访问和修改同一个计数器带来竞争条件race condition问题需要额外的同步机制这样会导致效率下降并增加复杂性。
c. 避免内存泄漏和未定义行为
使用堆内存而不是静态成员变量有助于防止内存泄漏和未定义行为。当引用计数归零时控制块及其管理的对象将被正确销毁。反之如果引用计数是静态成员变量那么当程序结束时它可能仍然有残留的非零计数导致资源未被释放。
d. 符合对象多态性和继承设计
堆上的控制块可以与对象一起动态分配并关联对象的多态行为如虚析构函数调用等。这种设计方式非常适合管理复杂对象的生命周期例如基于继承的对象结构。使用静态成员变量的设计将无法灵活应对多态性和继承的场景因为静态变量不依赖于具体对象实例。
总结
将引用计数放在堆上使 std::shared_ptr 能够灵活管理不同对象的生命周期支持多种使用场景包括多线程环境下的安全性动态对象管理防止内存泄露等。这些特点使得 std::shared_ptr 成为一个强大且安全的智能指针类型广泛应用于现代C编程。
3.1 shared_ptr的定制删除器
std::shared_ptr 的定制删除器Custom Deleter允许开发者在智能指针销毁其所管理的对象时自定义如何释放资源。这对于需要自定义资源管理的情况非常有用例如管理动态分配的内存、文件句柄、网络连接、数据库连接等。
为什么使用定制删除器
默认情况下std::shared_ptr 使用 delete 运算符来销毁它管理的对象。这对于大多数简单的动态分配对象如通过 new 分配的对象是足够的。但是有时候我们需要更复杂的资源管理比如
与C库的兼容性某些资源如用 malloc 分配的内存需要使用 free 而不是 delete 来释放。管理非内存资源如文件句柄、套接字、数据库连接等需要特定的函数来释放资源。调试或日志记录在删除对象时记录日志或执行其他调试操作。池化或缓存管理将对象返回到对象池而不是直接删除。
如何使用 shared_ptr 的定制删除器
std::shared_ptr 的构造函数可以接受一个删除器通常是一个函数指针、函数对象或 lambda 表达式。当 std::shared_ptr 不再需要管理的对象时它会调用这个删除器来释放资源。
示例使用 lambda 表达式作为删除器
以下是一个使用 lambda 表达式作为定制删除器的示例
#include iostream
#include memoryint main() {// 使用 malloc 分配内存int* p (int*)std::malloc(sizeof(int));if (p nullptr) {throw std::bad_alloc();}// 使用自定义删除器创建 shared_ptrstd::shared_ptrint ptr(p, [](int* p) {std::cout Using custom deleter to free memory.\n;std::free(p); // 使用 free 释放内存});*ptr 42; // 使用 shared_ptr 访问数据std::cout Value: *ptr std::endl;// 当 ptr 超出作用域时删除器将被调用释放内存return 0;
}在这个例子中std::shared_ptr 使用 malloc 分配的内存并提供了一个定制删除器lambda 表达式在内存释放时调用 free。这样确保了资源被正确释放。
示例管理文件句柄
你可以使用 std::shared_ptr 来管理一个打开的文件句柄并提供一个自定义删除器来关闭文件
#include iostream
#include memory
#include cstdio // 使用 C 标准库的文件操作函数int main() {// 打开一个文件std::shared_ptrFILE file(fopen(example.txt, w), [](FILE* fp) {if (fp) {std::cout Closing file.\n;fclose(fp); // 关闭文件}});if (!file) {std::cerr Failed to open file.\n;return 1;}// 使用文件句柄fprintf(file.get(), Hello, world!\n);// 当 file 超出作用域时文件将被自动关闭return 0;
}在这个示例中std::shared_ptr 管理一个 FILE* 指针文件句柄并在文件指针超出作用域时自动调用 fclose 关闭文件。
定制删除器的使用细节 定制删除器的存储std::shared_ptr 内部会存储删除器因此这个删除器本身也会占用一定的内存空间。通常删除器是一个小的函数对象如 lambda 表达式其开销可以忽略不计但对于复杂的删除器对象可能会增加一些内存使用。 删除器的类型删除器的类型是 std::shared_ptr 类型的一部分。因此不同的删除器会导致不同类型的 std::shared_ptr。例如 std::shared_ptrint p1(new int(10), [](int* p) { delete p; });
std::shared_ptrint p2(new int(20), std::default_deleteint());p1 和 p2 的类型虽然都是 std::shared_ptrint但它们的删除器类型不同因此无法相互赋值。 使用 std::function 存储删除器在需要将具有不同删除器的 std::shared_ptr 存储在一起时可以使用 std::function std::vectorstd::shared_ptrvoid resources;
resources.push_back(std::shared_ptrvoid(new int(10), [](void* p) { delete static_castint*(p); }));
resources.push_back(std::shared_ptrvoid(fopen(example.txt, w), [](void* fp) { fclose(static_castFILE*(fp)); }));定制删除器允许在 std::shared_ptr 管理的对象销毁时执行自定义的资源释放逻辑这对非内存资源的管理非常有用。可以使用函数指针、函数对象或 lambda 表达式作为定制删除器。定制删除器的使用确保了资源的正确释放避免了内存泄漏和资源泄漏。定制删除器的灵活性使 std::shared_ptr 成为管理各种资源的理想选择。
4. weak_ptr
std::weak_ptr 是 C11 引入的另一种智能指针用来解决 std::shared_ptr 引用计数可能导致的循环引用问题。它提供了一种弱引用的方式来引用一个对象而不影响该对象的引用计数。
主要作用 解决循环引用问题 当两个或多个对象相互引用对方的 std::shared_ptr 时会形成循环引用。这种情况下即使这些对象之间没有其他引用它们的引用计数也永远不会变为零导致内存泄漏。std::weak_ptr 可以打破这种循环引用的情况因为它不会增加引用计数。 提供一种安全的方式检查对象是否存在 std::weak_ptr 可以检查一个对象是否已经被销毁而不尝试访问该对象。使用 std::weak_ptr 时我们可以用它来创建一个 std::shared_ptr只有在对象仍然存在的情况下这样就可以安全地使用这个对象。
工作原理
std::weak_ptr 是一个指向由 std::shared_ptr 管理的对象的弱引用。它不会增加共享对象的引用计数use_count因此不会影响对象的生命周期。当你需要访问由 std::weak_ptr 引用的对象时你需要将它转换为一个 std::shared_ptr。如果对象仍然存在use_count 0这个操作将成功否则转换将产生一个空的 std::shared_ptr。
使用场景 避免循环引用 在对象之间存在相互依赖的情况下std::weak_ptr 可以打破循环引用。例如在实现一个树状或图状数据结构时父节点和子节点可以互相引用 #include iostream
#include memoryclass Node {
public:std::shared_ptrNode parent;std::vectorstd::shared_ptrNode children;Node() { std::cout Node created\n; }~Node() { std::cout Node destroyed\n; }
};int main() {std::shared_ptrNode parent std::make_sharedNode();std::shared_ptrNode child std::make_sharedNode();parent-children.push_back(child);child-parent parent; // 循环引用// parent和child的引用计数相互增加导致内存泄漏return 0;
}在上面的例子中parent和child相互引用形成一个循环引用。当程序结束时parent和child的引用计数都不会变为零因此它们都不会被销毁导致内存泄漏。 为了解决这个问题可以使用std::weak_ptr将父节点的引用变为弱引用 #include iostream
#include memory
#include vectorclass Node {
public:std::weak_ptrNode parent; // 父节点使用弱引用std::vectorstd::shared_ptrNode children;Node() { std::cout Node created\n; }~Node() { std::cout Node destroyed\n; }
};int main() {std::shared_ptrNode parent std::make_sharedNode();std::shared_ptrNode child std::make_sharedNode();parent-children.push_back(child);child-parent parent; // 弱引用不增加引用计数// 正常情况下parent和child的引用计数将达到零内存会被释放return 0;
}使用 std::weak_ptr 后父节点的引用不再增加子节点的引用计数这样当没有其他 std::shared_ptr 指向子节点时子节点将会被销毁从而避免了循环引用导致的内存泄漏。 临时访问共享对象 在某些情况下你只需要短时间访问一个共享对象而不希望延长它的生命周期。使用 std::weak_ptr 可以实现这种临时访问避免不必要的引用计数增加。
如何使用
以下是 std::weak_ptr 的基本用法示例大家可以自行跑一下
#include iostream
#include memoryint main() {// 创建一个 std::shared_ptrstd::shared_ptrint sp std::make_sharedint(42);// 创建一个 std::weak_ptr指向同一个对象std::weak_ptrint wp sp;// 检查对象是否仍然存在if (auto locked wp.lock()) { // 使用 lock() 获得 std::shared_ptrstd::cout 对象仍然存在值为: *locked \n;} else {std::cout 对象已销毁\n;}sp.reset(); // 手动释放 shared_ptr引用计数变为0对象被销毁// 再次检查对象是否存在if (auto locked wp.lock()) {std::cout 对象仍然存在值为: *locked \n;} else {std::cout 对象已销毁\n;}return 0;
}std::weak_ptr 通过提供一种不影响引用计数的弱引用方式解决了 std::shared_ptr 循环引用导致的内存泄漏问题并且允许安全地检查对象是否仍然存在。它在缓存、观察者模式和避免延长对象生命周期的场景中非常有用。
如果你能看到这里给你点个赞如果对你有帮助的话不妨点赞支持一下~