RefPtr和PassRefPtr基础[1]

本文介绍了WebKit项目中用于管理对象引用计数的智能指针技术:RefPtr与PassRefPtr。详细解释了这两种智能指针的工作原理及如何避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

历史:

     在WebKit中,许多对象采用了引用计数。这种模式是通过类的ref,deref成员函数来递增和递减对象的引用记数。调用一次ref必须调用一次deref。当对象的引用记数为0的时候,对象就被删除。WebKit中许多类创建的新对象引用记数都为0,这被称作是浮动状态(Floating State)。在浮动状态的对象必须调用ref,在删除之前必须调用deref。WebCore中许多类通过继承RefCounted模版类来实现这种模式。
在2005年的时候,我们发现存在很多内存泄漏的问题,特别实在WebCore编辑器代码中,这主要是由没有正确的使用ref和deref调用,还有就是创建的对象没有调用ref,依然是浮动状态。
     我们决定我们使用智能指针来解决这个问题,然而,一些前期的实验表明,智能指针导致引用记数的其他操纵影响性能。例如,一个函数使用智能指针来传递参数,函数返回时也使用这个智能指针作为返回值,仅仅在一个对象从一个智能指针移动到另外一个时,传递参数和返回函数值时就递增和递减引用记数2-4次。因此,我们寻求一种能够让我们使用智能指针又避免使用这种引用记数的性能流失的方法。
这种解决方案的灵感来源于C++的标准模版类auto_ptr。应用这种模式的对象在赋值的时候将传递了所有权。当你把一个auto_ptr传递给另外一个时,传递者变为0。
      Maciej Stachowiak设计了一对模版类,RefPtr和PassRefPtr,它为WebCore的引用记数实现了这种模式。
原始指针:

      在讨论如RefPtr模版类这类智能指针时,我们使用原始指针来构建,下面是使用原始指针写的规范的Setter函数。

  1. // example, not preferred style  
  2. class Document {  
  3.     [...]  
  4.     Title* m_title;  
  5. }  
  6. Document::Document(): m_title(0)  
  7. {  
  8. }  
  9. Document::~Document()  
  10. {  
  11.     if (m_title)  
  12.         m_title->deref();  
  13. }  
  14. void Document::setTitle(Title* t)  
  15. {  
  16.     if (t)  
  17.         t->ref();  
  18.     if (m_title)  
  19.         m_title->deref();  
  20.     m_title = t;  
  21. }  

RefPtr
RefPtr是一种简单的智能指针,主要是通过在传入值时调用ref,传出时调用deref。RefPtr工作在有ref和deref成员函数的对象上。下面是一个例子:

  1. // example, not preferred style  
  2.    
  3. class Document {  
  4.     [...]  
  5.     RefPtr<Title> m_title;  
  6. }  
  7. void Document::setTitle(Title* t)  
  8. {  
  9.     m_title = t;  
  10. }  

单独使用RefPtr会导致引用记数的流失,例如下面的例子:

  1. // example, not preferred style  
  2.    
  3. RefPtr<Node> createSpecialNode()  
  4. {  
  5.     RefPtr<Node> a = new Node;  
  6.     a->setSpecial(true);  
  7.     return a;  
  8. }  
  9. RefPtr<Node> b = createSpecialNode();  

从这个方面来说,假定Node对象开始时引用记数为0,当它被赋值到a时,递增引用记数为1。在创建返回值时递增引用记数到2,当a销毁的时候递减回1.然后在创建b的时候引用记数递增到2,在createSpecialNode函数的返回值销毁时递减到1.(这些分析忽略了编译器返回值优化的可能性,如果编译器这么做了,可能导致引用记数的流失)
当涉及到函数参数和返回值时,引用记数的流失的代价比较大,解决方法就是PassRefPtr。

PassRefPtr
PassRefPtr除过有一点区别其他都和RefPtr类似,当传递一个PassRefPtr,或者把PassRefPtr赋值到RefPtr或者另一PassRefPtr时,原始的指针值设置为0;操作没有做任何对引用记数的更改。下面看一个新的例子:

  1. // example, not preferred style  
  2. PassRefPtr<Node> createSpecialNode()  
  3. {  
  4.     PassRefPtr<Node> a = new Node;  
  5.     a->setSpecial(true);  
  6.     return a;  
  7. }  
  8. RefPtr<Node> b = createSpecialNode();  

Node对象开始时,引用记数为0,当被赋值到a时,引用记数加1,在返回值PassRefPtr创建时,a被设置为0,当创建b时,返回值设置为0。
然后,当我们开始使用PassRefPtr编码时,Safari团队发现当被赋值到另一个变量时,指针变为了0,这种很容易导致错误。

  1. // example, not preferred style  
  2.    
  3. static RefPtr<Ring> g_oneRingToRuleThemAll;  
  4. void finish(PassRefPtr<Ring> ring)  
  5. {  
  6.     g_oneRingToRuleThemAll = ring;  
  7.     ...  
  8.     ring->wear();  
  9. }  

在wear被调用时,ring已经是空指针了,为了避免这种情况,建议PassRefPtr只在作为函数参数,返回值和拷贝参数到RefPtr局部变量时使用。

  1. static RefPtr<Ring> g_oneRingToRuleThemAll;  
  2. void finish(PassRefPtr<Ring> prpRing)  
  3. {  
  4.     RefPtr<Ring> ring = prpRing;  
  5.     g_oneRingToRuleThemAll = ring;  
  6.     ...  
  7.     ring->wear();  
  8. }  

混合使用RefPtr和PassRefPtr

除过当传递参数或者函数返回值时这些情况外,建议使用RefPtr。,在打算把RefPtr所有权转移到PassRefPtr时,RefPtr类有一个release成员函数,它能够设置RefPtr到0,然后构建一个PassRefPtr对象,这期间没有改变引用记数。

  1. // example, assuming new Node starts with reference count 0  
  2.    
  3. PassRefPtr<Node> createSpecialNode()  
  4. {  
  5.     RefPtr<Node> a = new Node;  
  6.     a->setCreated(true);  
  7.     return a.release();  
  8. }  
  9. RefPtr<Node> b = createSpecialNode();  

这种方式保持了PassRefPtr的有效性,同时也降低了导致问题的可能性。

与原始指针混合使用

RefPtr使用get方法来获得一个原始指针

  1. printNode(stderr, a.get());  

然而,这些操作可以不使用get调用,而通过RefPtr和PassRefPtr直接完成。

  1. RefPtr<Node> a = createSpecialNode();  
  2. Node* b = getOrdinaryNode();  
  3. // the * operator  
  4. *a = value;  
  5. // the -> operator  
  6. a->clear();  
  7. // null check in an if statement  
  8. if (a)  
  9.     log("not empty");  
  10. // the ! operator  
  11. if (!a)  
  12.     log("empty");  
  13. // the == and != operators, mixing with raw pointers  
  14. if (a == b)  
  15.     log("equal");  
  16. if (a != b)  
  17.     log("not equal");  
  18. // some type casts  
  19. RefPtr<DerivedNode> d = static_pointer_cast<DerivedNode>(a);  

 

[参考]http://webkit.org/coding/RefPtr.html

转:http://blog.youkuaiyun.com/cnwarden/article/details/4628049


<think>我们正在解决C++编译时出现的undefinedreference错误,特别是针对一个名为jso1.cpp的文件。根据常见的C++开发经验,undefinedreference错误通常发生在链接阶段,表示编译器在目标文件中找不到某个函数或变量的定义。错误信息“undefinedreferenceto”后面通常会跟着一个函数名或变量名,说明链接器在尝试解析引用时找不到该符号的定义。可能的原因解决方案包括:1.函数或变量未定义:确保所有被引用的函数变量都有定义。检查是否拼写错误,或者是否在正确的文件中实现了该函数。2.链接时缺少目标文件:在编译命令中,确保将所有相关的源文件都编译成目标文件,并在链接时包含这些目标文件。例如,如果jso1.cpp中使用了另一个文件(比如jso2.cpp)中的函数,那么需要编译jso2.cpp并链接生成的目标文件。3.库文件未链接:如果使用了外部库,需要确保在链接时指定了该库。例如,使用-l选项指定库名(如-lmylib)。4.函数声明不一致:检查头文件中的函数声明与定义是否一致(包括名称、参数列表、返回类型、命名空间等)。5.模板函数的定义不可见:模板函数的定义通常需要放在头文件中,或者在使用时可见。如果模板函数的定义在.cpp文件中,而其他文件包含的是声明,则会导致链接错误。6.内联函数的定义不可见:内联函数的定义必须在使用它的每个翻译单元中可见,通常定义在头文件中。7.使用C语言编写的代码,在C++中使用时未用extern"C"声明:如果引用了C语言编写的库中的函数,需要在头文件中用extern"C"进行声明,以确保C++编译器使用C的链接规则。针对用户的问题,他提到了一个特定的文件jso1.cpp,因此我们需要检查这个文件。步骤建议:1.检查错误信息中提到的未定义符号(函数或变量)是否在jso1.cpp中或者在其他文件中定义。2.确保在编译时将所有需要的源文件一起编译,或者将已经编译好的目标文件链接起来。例如,如果项目有两个源文件:jso1.cppjso2.cpp,那么编译链接命令应该是:g++-oprogramjso1.cppjso2.cpp或者先分别编译再链接:g++-cjso1.cpp-ojso1.og++-cjso2.cpp-ojso2.og++-oprogramjso1.ojso2.o3.如果使用了库,确保在链接时指定了库路径(-L选项)库名(-l选项)。4.检查函数声明定义是否一致。例如,在头文件中声明了函数,在源文件中定义,确保声明定义匹配。5.如果是类成员函数,检查是否在类定义中声明了但没有实现(且没有标记为纯虚函数),或者实现时作用域运算符(::)前面的类名是否正确。6.注意命名空间:如果函数定义在某个命名空间中,使用时需要加上命名空间,或者使用using声明。7.如果使用了静态成员变量,需要在类外进行定义(分配存储空间)。由于用户没有提供具体的错误信息,我们只能给出一般性的解决方案。此外,用户提供的引用内容中,引用[1]提到了动态库的版本设置,虽然与链接错误没有直接关系,但提醒我们如果错误涉及动态库,那么可能需要检查动态库的路径版本是否匹配。引用[2][3][4]涉及Node.js,与当前问题无关。因此,我们重点从一般链接错误的角度给出建议。生成相关问题:1.如何具体分析undefinedreference错误信息?2.在大型项目中如何组织编译链接以避免此类错误?3.如何使用CMake来管理C++项目以避免链接错误?4.动态库静态库在链接时有什么不同?5.什么是C++中的extern"C",它如何解决链接问题?6.如何检查一个符号(函数或变量)是否在某个库中定义?</think>### C++ "undefined reference" 链接错误解决方案 **问题原因分析** "undefined reference to" 错误发生在**链接阶段**,表示编译器找到了函数/变量的声明,但链接器找不到其实际实现。常见原因: 1. **实现缺失** - 函数/类成员函数声明但未实现 - 静态成员变量声明但未定义(需单独定义) ```cpp // 头文件.h class MyClass { public: static int staticVar; // 声明 void missingFunc(); // 声明 }; // 源文件.cpp int MyClass::staticVar; // 必须定义!(错误示例:漏掉此行) // void MyClass::missingFunc() {} // 错误示例:未实现 ``` 2. **链接对象缺失** - 未将实现文件加入编译(如 `jso1.cpp` 依赖 `jso2.cpp` 但未编译后者) ```bash # 错误编译方式 g++ -o program jso1.cpp # 正确方式(添加依赖文件) g++ -o program jso1.cpp jso2.cpp ``` 3. **库链接问题** - 动态库/静态库未正确链接 ```bash # 需要显式链接数学库 g++ main.cpp -o main -lm ``` 4. **名称修饰不匹配** - C/C++ 混用时未使用 `extern "C"` ```cpp // C++ 调用 C 函数 extern "C" { #include "c_lib.h" } ``` --- **解决方案步骤** 1. **检查函数签名一致性** 确保头文件声明与源文件定义的**函数名、参数、返回类型、命名空间**完全一致: ```cpp // 声明 (header.h) namespace ns { void func(int x); } // 定义 (source.cpp) void ns::func(int x) { ... } // 正确 // void func(int x) { ... } // 错误:缺少命名空间 ``` 2. **验证所有源文件参与编译** 确认构建系统(Make/CMake)包含所有必要的实现文件: ```cmake # CMakeLists.txt 示例 add_executable(MyProgram jso1.cpp helper.cpp # 必须包含依赖文件 ) ``` 引用[1]中动态库配置也需确保链接路径正确。 3. **检查库链接** - 使用 `-l` 指定库名(如 `-lpthread`) - 使用 `-L` 指定库路径(如 `-L/usr/local/lib`) ```bash g++ jso1.cpp -o app -lmyLib -L./libs ``` 4. **处理模板类/函数** 模板实现必须放在头文件中(或显式实例化): ```cpp // 模板错误示例:实现放在.cpp文件 // 正确做法:将模板代码全部移至头文件 ``` 5. **静态成员定义** 类内静态成员需在类外单独定义: ```cpp // 类内声明 class Singleton { static Singleton* instance; }; // 类外定义(必须在.cpp文件) Singleton* Singleton::instance = nullptr; ``` --- **调试技巧** 1. 使用 `nm` 检查目标文件符号: ```bash nm -gC jso1.o | grep "missingFunction" ``` 2. 检查链接顺序:依赖库放在被依赖库后面 3. 确保没有拼写错误(区分大小写) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值