最近遇到這樣一個編譯問題,代碼是這樣的:
#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;
}
};