继承T并使用using T::T时,模板类中如何初始化const成员?
解决思路与实现方案
嘿,这问题我太懂了!咱们要解决的核心就是把开销大的计算型const成员函数换成const成员变量,同时还要保住原来的继承结构——毕竟大量函数都依赖B1/B2的指针/引用,可不能改这个关系。
核心问题分析
原来的is_early_bird()/is_hunting()是每次调用都重复计算的const函数,开销拉满;但如果换成const成员变量,我们得在对象构造时就完成初始化。不过之前用using T::T继承了基类构造函数,这些继承来的构造函数没法帮我们初始化派生类自己的const成员,所以得自定义构造函数来搞定这件事。
具体实现步骤
- 替换函数为const成员变量:把原来的成员函数改成
const bool类型的成员变量,比如bird类里的const bool is_early_bird_;。 - 用可变参数模板构造函数转发参数:因为B1和B2的构造函数参数不一样,我们需要写一个泛型构造函数,完美转发所有参数给基类T的构造函数,同时在初始化列表里计算并初始化const成员变量。
- 这里要注意:基类会先于派生类成员初始化,所以构造时
this->wake_up_time_已经是有效的,可以安全用来计算。
- 这里要注意:基类会先于派生类成员初始化,所以构造时
修改后的完整代码
#include <iostream> #include <ctime> #include <string> #include <memory> #include <utility> // 用于std::forward class A { protected: int wake_up_time_; public: A(int wake_up_time): wake_up_time_(wake_up_time) { } virtual void hello() const { std::cout << "I wake up at " << wake_up_time_; } }; class B1 : public A { public: std::string b; B1(int a, std::string b): A(a), b(std::move(b)) { } // 加std::move优化拷贝 }; class B2 : public A { public: int c; double d; B2(int a, int c, double d): A(a), c(c), d(d) { } }; template<class T> class bird : public T { const bool is_early_bird_; // 替换成const成员变量 public: // 可变参数模板构造函数,完美转发参数给基类T,同时初始化成员变量 template<typename... Args> bird(Args&&... args) : T(std::forward<Args>(args)...), is_early_bird_(this->wake_up_time_ < 6) {} void hello() const override { std::cout << (is_early_bird_ ? "I am an early bird!" : "Getting up is hard...") << std::endl; } }; template<class T> class cat : public T { const bool is_hunting_; // 替换成const成员变量 public: template<typename... Args> cat(Args&&... args) : T(std::forward<Args>(args)...), is_hunting_(this->wake_up_time_ < 5 || this->wake_up_time_ > 22) {} void hello() const override { std::cout << (is_hunting_ ? "Time to kill stuff" : "Time to sleep") << std::endl; } }; int main() { std::unique_ptr<A> ptr; { ptr.reset(new bird<B1>(5, "...")); std::cout << "B1 has value " << dynamic_cast<B1*>(ptr.get())->b << std::endl; } ptr->hello(); { ptr.reset(new cat<B1>(12, "xyz")); std::cout << "B1 has value " << dynamic_cast<B1*>(ptr.get())->b << std::endl; } ptr->hello(); { ptr.reset(new cat<B2>(24, 3, 12.5)); B2* l_ptr = dynamic_cast<B2*>(ptr.get()); std::cout << "B2 has value " << l_ptr->c << " and " << l_ptr->d << std::endl; } ptr->hello(); { ptr.reset(new B2(10, 7, 3.33)); B2* l_ptr = dynamic_cast<B2*>(ptr.get()); std::cout << "B2 has value " << l_ptr->c << " and " << l_ptr->d << std::endl; } ptr->hello(); return 0; }
关键细节解释
- 完美转发:用
std::forward<Args>(args)...可以保证参数的类型(左值/右值)被正确传递给基类构造函数,避免不必要的拷贝,提升效率。 - 初始化顺序:C++中基类的构造总是先于派生类成员的初始化,所以在初始化
is_early_bird_时,this->wake_up_time_已经由基类A(通过T的构造函数)初始化完成,完全可以安全使用。 - 兼容性保持:
bird<T>和cat<T>依然是T的派生类,所以dynamic_cast<B1*>或dynamic_cast<B2*>依然能正常工作,完全兼容原来那些接收B1/B2指针/引用的函数。
运行结果
修改后的代码运行结果和原代码完全一致:
B1 has value ... I am an early bird! B1 has value xyz Time to sleep B2 has value 3 and 12.5 Time to kill stuff B2 has value 7 and 3.33 I wake up at 10
内容的提问来源于stack exchange,提问作者Benjamin Christoffersen
相关产品推荐
相关产品推荐

