最近遇到这样一个编译问题,代码是这样的:
#include <memory>
#include <iostream>
class Base
{
public:
virtual ~Base() {}
};
class Derived : public Base
{
public:
~Derived() {}
};
void processSpBase(std::shared_ptr<Base> &p)
{
std::cout << p.use_count() << std::endl;
}
int main()
{
std::shared_ptr<Derived> p(new Derived);
std::cout << p.use_count() << std::endl;
processSpBase(p);
return 0;
}
使用VS2017编译报错:
main.cpp:24: error: C2664: “void processSpBase(std::shared_ptr<Base> &)”: 无法将参数 1 从“std::shared_ptr<Derived>”转换为“std::shared_ptr<Base> &”
对着这报错思考了一段时间,未果,中途考虑是难道shared_ptr需要显式转换?查到了std::static_pointer_cast这个标准库模板,把processSpBase调用的一行改成:
processSpBase(std::static_pointer_cast<Base>(p));
仍然报错:
main.cpp:24: error: C2664: “void processSpBase(std::shared_ptr<Base> &)”: 无法将参数 1 从“std::shared_ptr<Base>”转换为“std::shared_ptr<Base> &”
光从错误信息上看,更加费解了。
把原来的代码换了GCC 5.4.0编译,同样报错,但是错误原因写的非常清楚:
./main.cpp: In function ‘int main()’:
./main.cpp:24:20: error: invalid initialization of non-const reference of type ‘std::shared_ptr<Base>&’ from an rvalue of type ‘std::shared_ptr<Base>’
processSpBase(p);
^
In file included from /usr/include/c++/5/memory:82:0,
from ./main.cpp:1:
/usr/include/c++/5/bits/shared_ptr.h:221:2: note: after user-defined conversion: std::shared_ptr<_Tp>::shared_ptr(const std::shared_ptr<_Tp1>&) [with _Tp1 = Derived; <template-parameter-2-2> = void; _Tp = Base]
shared_ptr(const shared_ptr<_Tp1>& __r) noexcept
^
./main.cpp:16:6: note: initializing argument 1 of ‘void processSpBase(std::shared_ptr<Base>&)’
void processSpBase(std::shared_ptr<Base> &p)
^
无法把一个右值转换为一个non-const引用。这个右值是从哪里来的呢,很明显只能是从p隐式转换得到的,GCC下面的报错信息也提示到了这一点。
智能指针和裸指针不一样,裸指针Derived *天生的不必经过任何人为干涉而隐式转换为Base *。但是shared_ptr就没办法只靠编译器隐式转换为shared_ptr,必须要写转换构造函数(见C++ Primer 5e 7.5.4一节)来提供隐式转换机制。shared_ptr说到底也只是一个普普通通的模板而已,通过传入不同的模板类型参数而实例化得到的类之间是没有继承关系的,只是模板代码提供的转换构造函数让它看起来像有继承关系,但是转换构造函数会生成一个临时对象,如果没注意到这一点就容易编译不过。我们把processSpBase传入的参数加上一个const,就没有编译问题了。运行一下,结果输出为:
1
2
进入processSpBase后引用计数增加了,可见构造了一个新的shared_ptr。
最后说一下这个模板的转换构造函数是如何实现的,这里关键的一点是使用std::is_convertible来判断隐式转换前后的两个模板参数的类型是否可以转换,直接看代码:
template<typename T>
class FooTemplate
{
public:
FooTemplate() {}
template<typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value, void>::type>
FooTemplate(const FooTemplate<U> &rhs)
{
std::cout << "Oh! An implicit cast." << std::endl;
}
};