ODB 2.4.0 使用延迟指针 lazy_shared_ptr 时遇到的问题

最近在学习使用C++下的ORM库——ODB,来抽象对数据库的CURD,由于C++的ORM实在是太冷门了,ODB除了官方英语文档,几乎找不到其他好用的资料,所以在使用过程中也是遇到很多疑惑,也解决很多问题。近期遇到的一个源码上的bug更是折腾了我很久。写个博客记录一下。

问题描述

举个官方的例子,现在有两张表employees和employers(雇员和雇主),每一个employee行对应一个employer行,模型可以这样设计(省略了一些冗余的代码)

#include 
#include 

#pragma db object 
class Employee  // 雇员
{ 
    ... 

    #pragma db id auto 
    unsigned long id; 

    #pragma db not_null 
    odb::lazy_shared_ptr<Employer> employer; // 一对一关系
}; 


#pragma db object 
class Employer  // 雇主
{ 
    ... 

    #pragma db id auto 
    unsigned long id; 

    #pragma db value_not_null
  	std::vector<lazy_weak_ptr<Employee>> employees_ // 一对多关系
}; 

这里使用lazy_shared_ptr是为了延迟加载,考虑到如果直接使用shared_ptr指针,Employer的数据量比较大的话,每次读取一个employee,就会自动加载对应的employer,非常耗费时间和内存,但是我们又不一定会用到employer。虽然在一对一关系的情况下勉强还能接受,但是如果是一对多关系,那预加载的数据量可能很庞大,吃力不讨好。所以官方提供了lazy-ptr延迟指针帮我们解决这个问题,lazy_shared_ptr指向的对应的employer不会读取employee时立即加载,而是在我们需要时,使用load()方法显式地加载。这种方式更合理。

unsigned long search_id = 52; 
std::shared_ptr<employee> epee(db->load<employee>(search_id)); 
cout << epee->id << endl;
... // 一系列操作之后
epee->employer.load(); // 延迟加载
cout << epee->employer.name << endl;

到这一步以为一切都很顺利,直到编译代码的时候,问题出现了。

/usr/include/odb/lazy-ptr.ixx:1153:10: error: no match for ‘operator=(operand types are ‘std::shared_ptr<Employer>’ and ‘odb::object_traits<Employer>::pointer_type’ {aka ‘Employer*’})
 1153 |       p_ = i_.template load<T> (true); // Reset id.
      |       ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/10/memory:84,
                 from driver.cxx:4:
/usr/include/c++/10/bits/shared_ptr.h:358:19: note: candidate: ‘std::shared_ptr<_Tp>& std::shared_ptr<_Tp>::operator=(const std::shared_ptr<_Tp>&) [with _Tp = Employer]358 |       shared_ptr& operator=(const shared_ptr&) noexcept = default;
      |                   ^~~~~~~~
/usr/include/c++/10/bits/shared_ptr.h:358:29: note:   no known conversion for argument 1 from ‘odb::object_traits<Employer>::pointer_type’ {aka ‘Employer*’} to ‘const std::shared_ptr<Employer>&358 |       shared_ptr& operator=(const shared_ptr&) noexcept = default;
      |                             ^~~~~~~~~~~~~~~~~
/usr/include/c++/10/bits/shared_ptr.h:362:2: note: candidate: ‘template<class _Yp> std::shared_ptr<_Tp>::_Assignable<const std::shared_ptr<_Yp>&> std::shared_ptr<_Tp>::operator=(const std::shared_ptr<_Yp>&) [with _Yp = _Yp; _Tp = Article]362 |  operator=(const shared_ptr<_Yp>& __r) noexcept
      |  ^~~~~~~~ 

解决问题

看错误提示,应该是shared_ptr的问题,我一开始一头雾水,去网上查了好多资料,也没人提出同样的问题(想不到ODB真就这么冷门)。查资料查了半天无果,决定从源码入手解决问题。
问题出在源码中/usr/include/odb/lazy-ptr.ixx的第1153行代码

  template <class T>
  inline std::shared_ptr<T> lazy_shared_ptr<T>::
  load () const
  {
    if (!p_ && i_)
      p_ = i_.template load<T> (true); // Reset id. 就是这一行代码

    return p_;
  }

可以看出p_的类型应该是std::shared_ptr,检测i_.template load (true);发现它的返回值是T*指针类型,输出的错误告诉我们找不到将T*指针类型赋值给std::shared_ptr的操作。很明显,不能将一个原始指针直接赋值给一个智能指针(这个可以在各种博客和教程上查到),所以程序编译不通过,正确的做法应该是使用reset()方法,修改代码如下

  template <class T>
  inline std::shared_ptr<T> lazy_shared_ptr<T>::
  load () const
  {
    if (!p_ && i_)
      //p_ = i_.template load (true); // Reset id.
      p_.reset(i_.template load<T> (true)); // Reset id.

    return p_;
  }

重新编译代码,编译成功,问题解决!
/usr/include/odb/lazy-ptr.ixx第1551行也有同样的问题,可以一起改了。

总结

由于我使用的版本是2.4.0,是2015年发布的版本,当时C++11可能还有许多问题没有完善,导致这个版本也有对应的bug(比如我现在遇到的这个bug,不知道是不是因为当时shared_ptr还没有规定不能直接被原生指针赋值的问题),我看到现在2.5.0已经在测试了(2022年的),相信这里提到的问题应该能在2.5.0版本得到解决。考虑到2.5.0版本还没正式发布,我还是把2.4.0版本可能会遇到的这个bug贴出来,避免其他小伙伴掉坑里。

你可能感兴趣的:(c++,算法,开发语言)