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

5 分钟阅读

验证背景

#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

分别单独实现

代码实现如下:

#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
}

结论

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

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

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

更新时间: