C++ string 你不知道的坑
程序员的苦逼生活——每日在工作过程中,颠覆自己的认知
今天要说的是常用的 std::string 在工作中踩的一个坑,导致线上测试出现乱码,发了 Error message
例子一:直接上代码凶手
#include <iostream>
class Base {
public:
std::string mName;
};
int main() {
Base *a = new Base();
a->mName = 123; // 神奇的写法, 逃过了代码检测, 并且编译成功
delete a;
return 0;
}
一般对于 std::string 这种复杂结构类型,写错了赋值是会报编译错误的
本应写 "123",结果脑抽了写成 123也是常有的事情,但编译器不聪明了呢?按道理这是 std::string 的拷贝构造函数的实现啊,怎么会允许 int 类型进行赋值呢?
例子二:再来一个符合我们想象的场景
#include <iostream>
int main() {
std::string a = 123; // 此句会报错 conversion from ‘int’ to non-scalar type ‘std::string’
return 0;
}
error: conversion from ‘int’ to non-scalar type ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’} requested
3 | std::string a = 123;
这才对嘛,我写错了你不给我报错,还给我过了,这是编译器的锅还是语法糖的锅呢?
那就逐层剖析一下例子一为什么会导致这个事情的发生
主要分为两个点
- 类默认构造时,成员变量到底干了什么
std::string底层
类默认构造时,类成员变量到底干了什么
结论:调用每个类成员变量的默认构造函数
也就意味着,再a->mName 调用之前std::string mName 已经是一个对象了,所以这个时候 = 不再调用拷贝构造函数,调用的是operator= 重载运算符。这个原因就间接造成与发现了今日的·闹剧·了。
std::string 底层
| Type | Definition |
|---|---|
| std::string | std::basic_string |
我们可以在 cppreference or gcc 源码中看到 std::string 其实就是 std::basic_string<char> 封装的对象,底层还是 char
这里不会全对
std::string进行全剖析,只针对问题进行讲解
那从"类默认构造时,类成员变量到底干了什么" 结论中提到了,原因是因为这个时候 = 不再调用拷贝构造函数,调用的是operator= 重载运算符所导致的。那么在std::string 中的拷贝构造函数实现与=运算符重载是如何撰写的
basic_string( const basic_string& other );
basic_string( basic_string&& other ) noexcept;
basic_string& operator=( CharT ch );
由于basic_string 的贝构造函数实现与=运算符重载实现实在太多了,所以只摘抄了一部分有价值的
可以看到拷贝构造函数的实现是没有 char 对象传参的实现的,而operator= 就有,而operator=中从char又可以与int 进行隐式转换,所以就导致了线上乱码的出现,Error message的错误。
至于为什么要有basic_string& operator=( CharT ch ); 这个实现,我思考片刻,既然在拷贝构造函数侧已经进行了char 对象的限制了,为什么在重载的时候又可以赋值进来,这不不公平吗!!! 但是考虑到不同用户不同的语法习惯,惘然下定论也是程序员的大忌,也希望大家能怼回我,给我解释解释为什么?还是说C++ 委员会就蛋疼。