C++传const char*构造SharedString两种初始化结果不同原因
以下是复现问题的测试代码:
template<typename T> class SharedValue { public: SharedValue(const T& t): valuePtr(new T(t)) {} const SharedValue& operator = (const T &t) { *valuePtr = t; return *this; } protected: dd_shared_ptr<T> valuePtr; }; typedef SharedValue<std::string> SharedString; int main(int argc, const char * argv[]) { // 编译通过 SharedString str(argv[0]); // 编译失败 SharedString str2 = argv[0]; return 0; }
编译报错信息:
No viable conversion from 'const char *' to 'SharedString' (aka 'SharedValue<basic_string<char, char_traits
, allocator >>')
核心原因
两种写法对应C++中两类完全不同的初始化规则,对隐式类型转换的限制不同,直接导致编译结果差异:
SharedString str(argv[0]);是直接初始化:编译器直接在目标内存位置匹配参数最合适的构造函数调用,允许构造函数形参参与一次合法的隐式转换。SharedString str2 = argv[0];是拷贝初始化:编译器需要先把等号右侧的值隐式转换为左侧的目标类型(即SharedString),再用转换得到的临时对象构造目标变量,整个隐式转换序列最多只允许1次用户定义转换,不允许多次连续的用户定义转换。
注意:类中重载的
operator=完全不参与本次初始化流程,赋值运算符只有在对象已经构造完成、对已有对象重新赋值时才会触发,不要把初始化阶段的=符号和赋值操作混淆。
具体编译行为解释
直接初始化编译通过的逻辑
argv[0]的原生类型是const char*,SharedString本质是SharedValue<std::string>,其构造函数形参类型为const std::string&。
直接初始化场景下,编译器只需要执行1次用户定义转换:调用std::string的构造函数把const char*转为const std::string&匹配的临时对象,就能直接调用SharedValue的构造函数完成初始化,整个过程符合语法要求,因此编译正常。
拷贝初始化编译失败的逻辑
拷贝初始化要求右侧值最多经过1次用户定义转换就能得到SharedString类型,但当前场景下完成转换需要连续2次用户定义转换:
- 第一次:
const char*隐式转换为std::string(由std::string的单参数构造函数支持) - 第二次:
std::string隐式转换为SharedString(由SharedValue的单参数构造函数支持)
C++语法明确规定隐式转换序列中最多只能出现1次用户定义转换,因此编译器找不到合法的转换路径,抛出类型不匹配的编译错误。
修复方式
如果要让拷贝初始化的写法编译通过,只需要手动完成第一次转换,把隐式转换链里的用户定义转换次数降到1次即可:
// 手动将const char*转为std::string,后续仅需要1次用户定义转换即可得到SharedString SharedString str2 = std::string(argv[0]);
如果给SharedValue的单参数构造函数加上explicit关键字,会直接禁止std::string到SharedValue的隐式转换,此时上述拷贝初始化写法也会编译失败,只能使用直接初始化的方式构造对象。
内容的提问来源于stack exchange,提问作者ERiSED

