C++ 关于强制转换符构造临时对象的探索
验证背景
#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 不是标准库的对象,其源码繁杂且找不到手册,所以猜测 UnicodeString
与 AnsiString
的实现,分为两种:
分别单独实现
UnicodeString
继承自AnsiString
分别单独实现
代码实现如下:
#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
运行结果:
不会崩溃。
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
运行结果:
不会崩溃。
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
运行结果:
不会崩溃。
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++
运行结果
不会崩溃
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
#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
运行结果:
不会崩溃。
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
运行结果:
不会崩溃。
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
运行结果:
不会崩溃。
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++
运行结果
不会崩溃
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
}
结论
-
如果按照我们设想的
UnicodeString
的两种实现方式, 使用强制转换符, 在编译器C++ Builder
、VS2010
、VS2019
和g++
都会构造临时对象。 -
该临时对象在该行结束后马上析构。所以使用从临时对象中获取的指针可能会有问题,但
C++ Builder
、VS2010
、VS2019
和g++
没有出现编译警告和运行崩溃。 -
获取临时对象的引用在
C++ Builder
和VS2010
中会有编译警告。在VS2019
、g++
中会直接编译错误。