當派生類繼承模板化基類時,直接調用基類成員函數會導致編譯錯誤:
template <typename Company>
class MsgSender {
public:
void sendClear(const MsgInfo& info) { ... }
};
template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info) {
sendClear(info); // ❌ 編譯錯誤:未找到 sendClear
}
};
原因:C++ 模板實例化是延遲的,編譯器在解析派生類定義時,尚不知道 Company 具體類型,因此無法確定基類 MsgSender<Company> 具體有哪些成員。為了在編譯期捕獲錯誤,標準規定非依賴名稱(非模板參數相關的名稱)在模板定義階段必須可查找到,否則報錯。
this-> 前綴(推薦)void sendClearMsg(const MsgInfo& info) {
this->sendClear(info); // ✅ 編譯器知道這是依賴名稱,延遲到實例化時查找
}
using 聲明引入template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; // ✅ 將基類成員引入派生類作用域
void sendClearMsg(const MsgInfo& info) {
sendClear(info); // 現在可直接調用
}
};
Base<T>::member(不推薦用於虛函數)void sendClearMsg(const MsgInfo& info) {
MsgSender<Company>::sendClear(info); // ✅ 編譯通過
}
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class CompanyCaroline {
public:
void sendCleartext(const string& msg) { cout << "Cleartext: " << msg << endl; }
void sendEncrypted(const string& msg) { cout << "Encrypted: " << msg << endl; }
};
struct MsgInfo { string cleartext; string encrypted; };
template <typename Company>
class MsgSender {
public:
void sendClear(const MsgInfo& info) {
Company c; c.sendCleartext(info.cleartext);
}
void sendSecret(const MsgInfo& info) {
Company c; c.sendEncrypted(info.encrypted);
}
};
// 方案 1:this->
template <typename Company>
class LoggingMsgSender1 : public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info) {
cout << "Log Begin: ";
this->sendClear(info); // 依賴名稱,延遲查找
}
};
// 方案 2:using
template <typename Company>
class LoggingMsgSender2 : public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear;
void sendClearMsg(const MsgInfo& info) {
cout << "Log Begin: ";
sendClear(info); // 直接可見
}
};
int main() {
MsgInfo mi{"Hello", "Secret"};
LoggingMsgSender1<CompanyCaroline> lms1;
lms1.sendClearMsg(mi);
LoggingMsgSender2<CompanyCaroline> lms2;
lms2.sendClearMsg(mi);
return 0;
}
| 階段 | 時機 | 查找範圍 | 非依賴名稱 | 依賴名稱 |
|---|---|---|---|---|
| 第一階段 | 模板定義解析時 | 全局/命名空間作用域、基類非模板部分 | ✅ 必須找到 | ⏳ 延遲 |
| 第二階段 | 模板實例化時 | 上述 + 基類模板實例化後的完整作用域 | 再次驗證 | ✅ 真正查找 |
sendClear 是依賴名稱(依賴於模板參數 Company),故第一階段不查找。this->sendClear 強制標記為依賴名稱;using 在派生類作用域引入聲明,使其成為非依賴名稱但已在第二階段可見。
this->:最通用、不破壞虛函數、無需額外聲明using:若需調用多個基類成員,寫一次 using Base<T>::func1; using Base<T>::func2; 更清爽typename Base<T>::Type 或 Base<T>::static_memberC++ 模板派生類訪問基類成員是「兩階段名稱查找」規則的直接體現。掌握 this->、using、顯式資格限定三種手段的適用場景與優劣,能讓你在編寫模板庫、CRTP(奇異遞歸模板模式)等高級元編程代碼時遊刃有餘,避免無謂的編譯錯誤與運行時多態失效。