友元在C++中意味著不好的設(shè)計,因忽略訪問屬性的限制,破壞了封裝性。我卻很喜歡這個特性。
特別說明,這個特性適合庫作者,一般的使用者可能意義不大,尤其是作為代碼的搬運工。
在非模版的設(shè)計中,有些時候,在寫目標類的時候尚不能確定那個類成為友元類,只能后期增加友元類的聲明,這就導(dǎo)致目標類的代碼不能封閉。模版可以解決這個問題,將友元類的設(shè)置延后到編譯階段。
比如有個日志類,有私有的方法和私有數(shù)據(jù)成員。想要直接訪問私有數(shù)據(jù)成員,除了用公開的get/set外,就只能用友元了。通過模版,這個myLog類的代碼就能封閉了。
template<typename T>
class myLog
{
friend T;
std::string str;
void print()
{
std::cout << str << std::endl;
}
public:
myLog() = default;
};
class writeDataToLog
{
private:
using Log = myLog<writeDataToLog>;
Log _log;
public:
void setval(std::string data) {
_log.str = data;
}
void print()
{
_log.print();
}
};
重要的就是friend T;這一句,在writeDataToLog里就可以直接變身為myLog的友元,調(diào)用私有的變量和函數(shù)。
如果T是普通類型如int,double等,這一句是被編譯器忽略的,不會有副作用。所以如果定義一個別名來直接使用類型無關(guān)的myLog:
using myLog_t = myLog<void>;
myLog_t log;
如果還不能說明友元模版的必要性,我們看看另外一個例子(重點在第14行),這是個使用引用計數(shù)的智能指針的片段:
template<typename T>
class SmartPointer
{
T* pointer = nullptr;
ReferenceCount* refcount = nullptr;
//友元模板,讓其它類型的智能指針可以直接訪問私有變量
template<typename>
friend class SmartPointer;
//...
//有繼承關(guān)系的構(gòu)造,向上轉(zhuǎn)換,U繼承自T
template<typename U, typename = std::enable_if_t<std::is_base_of_v<T, U>>>
SmartPointer(SmartPointer<U> const& other)
{
if (pointer = other.pointer, refcount = other.refcount) //友元模板的設(shè)置才能訪問other的私有變量
{
refcount->increment();
}
}
//...
}
如果像讓不同的每個myLog的實例類都能互相訪問私有數(shù)據(jù),則可以這么做:
template<typename T>
class myLog
{
//...
template<typename>
friend class myLog;
//...
}
這樣,myLog家族類之間就沒有秘密了。仔細琢磨,這挺神奇的。