为何已有合适模板时仍需编写operator=的定义(而非声明)
嘿,我来给你掰扯清楚这个问题——为啥明明有看起来合适的模板,还非得把operator=的定义(不只是声明)写出来不可?先给你看我用来追踪对象构造、析构的自洽C++示例代码,咱们从代码里找答案:
#include<iostream> #include<type_traits> class number; template<typename T> double get_value(T const&); template<typename T> double get_value(T const& x){ return (double)x; } template<typename T> requires std::is_same_v<T,number> double get_value(T const& rhs){ return rhs.get(); } class number{ private: const size_t index; double value; static size_t get_index(){ static size_t count=0; return count++; } public: number(): index(get_index()), value(0) { std::cout<<"number_"<<index<<"::number(void).\n"; } ~number(){ std::cout<<"number_"<<index<<"::~number()\n"; } double const& get()const{ return value; } void print(std::ostream& ooo=std::cout)const { ooo<<"number_"<<index<<": value = "<<value<<"\n"; } };
咱们先看这个number类,它有个const size_t index成员——这是个const成员,编译器默认生成的赋值运算符会尝试给它赋值,这肯定会报错,毕竟const成员不能被修改对吧?那要是我们想写个模板版的赋值运算符,用get_value模板来统一处理内置类型和number类的赋值逻辑呢?
这时候你可能会想:我先声明一下这个模板赋值运算符行不行?就像普通函数那样,声明放头文件,定义放cpp?哎,还真不行!
举个栗子,如果你只写个声明:
template<typename T> number& operator=(T const& rhs); // 只有声明,没定义
然后在main函数里这么用:
int main(){ number num1; num1 = 3.14; num1.print(); return 0; }
编译的时候可能没问题,但链接器直接给你报错:找不到number& number::operator=<double>(double const&)的定义。为啥?
因为模板函数跟普通函数不一样!普通函数的声明是告诉编译器“这个函数存在,代码在别的地方”,链接器会去其他目标文件里找。但模板是个“函数蓝图”,编译器要在你用到它的时候,根据传入的类型生成具体的函数代码——如果只有声明没有定义,编译器根本拿不到蓝图的细节,没法生成对应的赋值运算符代码,链接器自然找不到实例化后的函数体。
那回到咱们的问题,就算有get_value这种合适的模板来处理赋值逻辑,你写的模板版operator=也必须有完整定义,要么把定义写在头文件里(让所有用到的翻译单元都能看到),要么显式实例化你需要的所有类型。只写声明的话,相当于你只告诉编译器“我有这么个东西”,但没给它生成代码的图纸,它根本没法干活。
还有个点要注意:像number这种带const成员的类,默认赋值运算符本身就不可用,你必须自定义赋值逻辑。如果选择用模板版的operator=,那定义更是必不可少——毕竟你得告诉编译器,当给number对象赋一个double、int或者另一个number时,具体该怎么处理那个const的index成员(比如跳过它,只赋值value)。
总结一下就是:
- 模板版
operator=本质是模板函数,只有声明的话,编译器没法生成具体的实例代码,链接阶段必报错; - 带const或引用成员的类,默认赋值运算符失效,必须自定义;用模板实现的话,定义是刚需;
- 普通函数可以声明定义分离,但模板函数不行(除非显式实例化),因为它是“按需生成”的,必须在实例化时看到完整定义。
备注:内容来源于stack exchange,提问作者user23311233

