0%

C++ 关于强制转换符构造临时对象的探索

验证背景

1
2
3
4
5
6
7
8
#define TO_STR(x)   ((AnsiString)x).c_str()

BOOL Dict::GetID(TcxComboBox *cb, UINT32 *pulID)
{
CHAR *szValue = TO_STR(cb->Text);

return GetID(szValue, pulID);
}

上面的代码中 TO_STR(x) 宏定义,我理解为通过 x 构造出一个临时 AnsiString 的对象, 并对该临时对象调用成员函数 c_str() 获得指向其内部的 const char *
由于匿名的临时对象的生命周期是该行结束即结束,所以获取到的 const char * 指针不可使用。
为了探究这个问题,使用以下代码来验证我的想法。

验证过程

由于 UnicodeString 不是标准库的对象,其源码繁杂且找不到手册,所以猜测 UnicodeStringAnsiString 的实现,分为两种:

  • 分别单独实现
  • UnicodeString 继承自 AnsiString

分别单独实现

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
#include <string.h>

#define LOG std::cout

namespace gos
{
template<class T>
std::string to_string(const T* dec)
{
char acText[32];
sprintf(acText, "0x%X", (size_t)dec);
return acText;
}
}

class AnsiString1
{
public:
/// 构造函数
AnsiString1()
{
str = new char[5];
memset(str, 0, sizeof(str));
LOG << "AnsiString1 构造函数 this: " << gos::to_string(this) << ", str: " << gos::to_string(str) << '\n';
}

/// 拷贝构造函数
AnsiString1(const AnsiString1& obj)
{
str = new char[5];
memset(str, 0, sizeof(str));
sprintf(str, obj.str);
LOG << "AnsiString1 拷贝构造函数 this: " << gos::to_string(this) << ", new str(" << gos::to_string(str) << ")" << '\n';
}

/// 析构函数
~AnsiString1()
{
delete[] str;
LOG << "~AnsiString1 析构函数 this: " << gos::to_string(this) << ", delete str("<< gos::to_string(str) << ")" << '\n';
}

const char* c_str()
{
return str;
}

private:
char* str;
};

class UnicodeString1
{
public:
UnicodeString1()
{
LOG << "UnicodeString1 构造函数 this: " << gos::to_string(this) << '\n';
}

~UnicodeString1()
{
LOG << "~UnicodeString1 析构函数" << gos::to_string(this) << '\n';
}

operator AnsiString1()
{
LOG << "operator AnsiString1(): " << gos::to_string(this) << '\n';
return m;
}

private:
AnsiString1 m;
};

C++ Builder 运行结果:

不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0x19FE64, str: 0xA926560
/// UnicodeString1 构造函数 this: 0x19FE64
const char* sz = ((AnsiString1)obj).c_str();
/// operator AnsiString1(): 0x19FE64
/// AnsiString1 拷贝构造函数 this: 0x19FE60, new str(0xA926520)
/// AnsiString1 拷贝构造函数 this: 0x19FE5C, new str(0xA926510)
/// ~AnsiString1 析构函数 this: 0x19FE5C, delete str(0xA926510)
/// ~AnsiString1 析构函数 this: 0x19FE60, delete str(0xA926520)
LOG << "使用字符串: " << gos::to_string(sz) << '\n';
/// 使用字符串: 0xA926510

/// AnsiString1(obj); ///< E2238 Multiple declaration for 'obj'
/// AnsiString1& r = (AnsiString1)obj; ///< E2357 Reference initialized with 'AnsiString1', needs lvalue of type 'AnsiString1'
}

VS2010 运行结果:

不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0xC3F914, str: 0x31A9F30
/// UnicodeString1 构造函数 this: 0xC3F914
const char* sz = ((AnsiString1)obj).c_str();
/// operator AnsiString1(): 0xC3F914
/// AnsiString1 拷贝构造函数 this: 0xC3F4FC, new str(0x31AA5B8)
/// AnsiString1 拷贝构造函数 this: 0xC3F4F0, new str(0x31AA600)
/// ~AnsiString1 析构函数 this: 0xC3F4F0, delete str(0x31AA600)
/// ~AnsiString1 析构函数 this: 0xC3F4FC, delete str(0x31AA5B8)
LOG << "使用字符串 sz: " << gos::to_string(sz) << '\n';
/// 使用字符串 sz: 0x31AA600
AnsiString1& r = (AnsiString1)obj;
/// operator AnsiString1(): 0xC3F914
/// AnsiString1 拷贝构造函数 this: 0xC3F518, new str(0x31AA5B8)
/// AnsiString1 拷贝构造函数 this: 0xC3F8F0, new str(0x31AA600)
/// ~AnsiString1 析构函数 this: 0xC3F518, delete str(0x31AA5B8)
const char* sz1 = r.c_str(); ///< warning C4239: 使用了非标准扩展:“初始化”: 从“AnsiString1”转换到“AnsiString1 &” 非常量引用只能绑定到左值
LOG << "使用字符串 sz1: " << gos::to_string(sz1) << '\n';
/// 使用字符串 sz1: 0x31AA600

/// 编译不通过的代码
/// AnsiString1(obj); ///< error C2371: “obj”: 重定义;不同的基类型
}

VS2019 运行结果:

不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0x8FFDF0, str: 0xA42310
/// UnicodeString1 构造函数 this: 0x8FFDF0
const char* sz = ((AnsiString1)obj).c_str();
/// operator AnsiString1(): 0x8FFDF0
/// AnsiString1 拷贝构造函数 this: 0x8FFD0C, new str(0xA42498)
/// AnsiString1 拷贝构造函数 this: 0x8FFD18, new str(0xA42070)
/// ~AnsiString1 析构函数 this: 0x8FFD18, delete str(0xA42070)
/// ~AnsiString1 析构函数 this: 0x8FFD0C, delete str(0xA42498)
LOG << "使用字符串 sz: " << gos::to_string(sz) << '\n';
/// 使用字符串 sz: 0xA42070

/// 编译不通过的代码
/// AnsiString1(obj); ///< error C2371: “obj”: 重定义;不同的基类型
/// AnsiString1& r = (AnsiString1)obj; ///< error C2440: “初始化”: 无法从“AnsiString1”转换为“AnsiString1 &”, 非常量引用只能绑定到左值
}

g++ 运行结果

不会崩溃

1
2
3
4
5
6
7
8
9
10
11
12
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0x53EDAFC0, str: 0x9C2010
/// UnicodeString1 构造函数 this: 0x53EDAFC0
const char* sz = ((AnsiString1)obj).c_str();
/// operator AnsiString1(): 0x53EDAFC0
/// AnsiString1 拷贝构造函数 this: 0x53EDAFC8, new str(0x9C2090)
/// ~AnsiString1 析构函数 this: 0x53EDAFC8, delete str(0x9C2090)
LOG << "使用字符串 sz: " << gos::to_string(sz) << '\n';
/// 使用字符串 sz: 0x9C2090~UnicodeString1 析构函数0x53EDAFC0
}

UnicodeString 继承自 AnsiString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <string.h>

#define LOG std::cout

namespace gos
{
template<class T>
std::string to_string(const T* dec)
{
char acText[32];
sprintf(acText, "0x%X", (size_t)dec);
return acText;
}
}

class AnsiString1
{
public:
/// 构造函数
AnsiString1()
{
str = new char[5];
memset(str, 0, sizeof(str));
LOG << "AnsiString1 构造函数 this: " << gos::to_string(this) << ", str: " << gos::to_string(str) << '\n';
}

/// 拷贝构造函数
AnsiString1(const AnsiString1& obj)
{
str = new char[5];
memset(str, 0, sizeof(str));
sprintf(str, obj.str);
LOG << "AnsiString1 拷贝构造函数 this: " << gos::to_string(this) << ", new str(" << gos::to_string(str) << ")" << '\n';
}

/// 析构函数
~AnsiString1()
{
delete[] str;
LOG << "~AnsiString1 析构函数 this: " << gos::to_string(this) << ", delete str("<< gos::to_string(str) << ")" << '\n';
}

const char* c_str()
{
return str;
}

private:
char* str;
};

class UnicodeString1 : public AnsiString1
{
public:
UnicodeString1()
{
LOG << "UnicodeString1 构造函数 this: " << gos::to_string(this) << '\n';
}

~UnicodeString1()
{
LOG << "~UnicodeString1 析构函数" << gos::to_string(this) << '\n';
}

operator AnsiString1()
{
LOG << "operator AnsiString1(): " << gos::to_string(this) << '\n';
return *(AnsiString1*)this;
}
};

C++ Builder 运行结果:

不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0x19FE64, str: 0xA966560
/// UnicodeString1 构造函数 this: 0x19FE64
const char* sz = ((AnsiString1)obj).c_str() << '\n';
/// AnsiString1 拷贝构造函数 this: 0x19FE60, new str(0xA966520)
/// ~AnsiString1 析构函数 this: 0x19FE60, delete str(0xA966520)
LOG << "使用字符串: " << gos::to_string(sz) << '\n';
/// 使用字符串: 0xA966520

/// AnsiString1(obj); ///< E2238 Multiple declaration for 'obj'
/// AnsiString1& r = (AnsiString1)obj; ///< E2357 Reference initialized with 'AnsiString1', needs lvalue of type 'AnsiString1'
}

VS2010 运行结果:

不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0xCFFA1C, str: 0x2C49F30
/// UnicodeString1 构造函数 this: 0xCFFA1C
const char* sz = ((AnsiString1)obj).c_str();
/// AnsiString1 拷贝构造函数 this: 0xCFF610, new str(0x2C4A5B8)
/// ~AnsiString1 析构函数 this: 0xCFF610, delete str(0x2C4A5B8)
LOG << "使用字符串 sz: " << gos::to_string(sz) << '\n';
/// 使用字符串 sz: 0x2C4A5B8
AnsiString1& r = (AnsiString1)obj; ///< warning C4239: 使用了非标准扩展:“初始化”: 从“AnsiString1”转换到“AnsiString1 &” 非常量引用只能绑定到左值
/// AnsiString1 拷贝构造函数 this: 0xCFF9F8, new str(0x2C4A5B8)
const char* sz1 = r.c_str();
LOG << "使用字符串 sz1: " << gos::to_string(sz1) << '\n';
/// 使用字符串 sz1: 0x2C4A5B8

/// 编译不过的代码
/// AnsiString1(obj); ///< error C2371: “obj”: 重定义;不同的基类型
}

VS2019 运行结果:

不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0x3AFCD0, str: 0x682428
/// UnicodeString1 构造函数 this: 0x3AFCD0
const char* sz = ((AnsiString1)obj).c_str();
/// AnsiString1 拷贝构造函数 this: 0x3AFBF8, new str(0x6823F0)
/// ~AnsiString1 析构函数 this: 0x3AFBF8, delete str(0x6823F0)
LOG << "使用字符串 sz: " << gos::to_string(sz) << '\n';
/// 使用字符串 sz: 0x6823F0

/// 编译不过的代码
/// AnsiString1& r = (AnsiString1)obj; ///< error C2440: “初始化”: 无法从“AnsiString1”转换为“AnsiString1 &”, 非常量引用只能绑定到左值
/// AnsiString1(obj); ///< error C2371: “obj”: 重定义;不同的基类型
}

g++ 运行结果

不会崩溃

1
2
3
4
5
6
7
8
9
10
11
void test()
{
UnicodeString1 obj;
/// AnsiString1 构造函数 this: 0xB512D870, str: 0x1D17010
/// UnicodeString1 构造函数 this: 0xB512D870
const char* sz = ((AnsiString1)obj).c_str();
/// AnsiString1 拷贝构造函数 this: 0xB512D878, new str(0x1D17090)
/// ~AnsiString1 析构函数 this: 0xB512D878, delete str(0x1D17090)
LOG << "使用字符串 sz: " << gos::to_string(sz) << '\n';
/// 使用字符串 sz: 0x1D17090
}

结论

  1. 如果按照我们设想的 UnicodeString 的两种实现方式, 使用强制转换符, 在编译器 C++ BuilderVS2010VS2019g++ 都会构造临时对象

  2. 该临时对象在该行结束后马上析构。所以使用从临时对象中获取的指针可能会有问题,但C++ BuilderVS2010VS2019g++ 没有出现编译警告和运行崩溃

  3. 获取临时对象的引用在 C++ BuilderVS2010 中会有编译警告。在 VS2019g++ 中会直接编译错误。