C++:联合体析构函数

4

联合是用户定义的数据或类类型,任何时候都只包含其成员列表中的一个对象。假设需要动态分配所有可能的候选成员。例如:

// Union Destructor
#include <string>
using namespace std;

union Person
{
private:
    char* szName;
    char* szJobTitle;
public:
    Person() : szName (nullptr), szJobTitle (nullptr) {}
    Person (const string& strName, const string& strJob)
    {
        szName = new char[strName.size()];
        strcpy (szName, strName.c_str());

        szJobTitle = new char [strJob.size()];
        strcpy (szJobTitle, strJob.c_str());    // obvious, both fields points at same location i.e. szJobTitle
    }
    ~Person()   // Visual Studio 2010 shows that both szName and szJobTitle
    {           // points to same location.
        if (szName) {
            delete[] szName;     // Program crashes here.
            szName = nullptr;  // to avoid deleting already deleted location(!)
        }
        if (szJobTitle)
            delete[] szJobTitle;
    }
};

int main()
{
    Person you ("your_name", "your_jobTitle");
    return 0;
}

上面的程序在 ~Person 的第一个 delete 语句处崩溃(当 szName 包含有效的内存位置时,为什么?)。
正确的析构函数实现方式是什么?
同样地,如果我的类包含联合成员,如何销毁类对象(如何编写包含 Union 的类的析构函数)?
4个回答

4
因为它们共享相同的内存,所以您一次只能使用联合体的一个成员。然而,在构造函数中,您初始化了两个成员,这会互相覆盖,然后在析构函数中,您最终将其释放两次。根据字段名称,您正在尝试像使用结构体一样使用它(需要使用结构体)。
尽管如此,如果您需要一个联合体,那么您可能需要一个结构体作为一种包装,它具有表示正在使用的成员的某个ID,以及处理资源的构造函数和析构函数。
另外-您的数组太小了。size()返回字符数,但是如果您使用char*作为字符串类型,则需要空间来处理结束符null-character (\0)。
如果需要联合体,请尝试使用Boost.Variant。它比普通联合体更容易使用。

“结构体是一种信封,其中包含代表正在使用的成员的某些ID”,也称为标记联合 - Andrew

2
您正在使用delete,而应该使用delete [],因为您已经使用了new []而不是new
请将以下代码更改为:
delete szName;
delete szJobTitle;

这些:
delete [] szName;
delete [] szJobTitle;

顺便说一下,在析构函数中的if条件是没有意义的。我的意思是,如果一个指针是nullptr,那么写delete ptr;是安全的。
A *ptr = nullptr;
delete ptr; //Okay! No need to ensure ptr is non-null

除此之外,您正在违反三个(或五个,在C++11中)规则: 请实现它们。

1

上面的程序在~Person中的第一个删除语句崩溃了(当szName包含有效内存位置时,为什么?)。

我没有编译器(或时间编译您的代码),但是(除了Nawaz解决的问题之外),我猜测这是因为您将union成员视为类成员。在您的union中,szName和szJobTitle应该被看作具有相同地址的两个变量:

Person (const string& strName, const string& strJob)
{
    szName = new char[strName.size()];
    strcpy (szName, strName.c_str());

    szJobTitle = new char [strJob.size()]; // this creates memory leak (1)
    strcpy (szJobTitle, strJob.c_str());
}

您会出现内存泄漏是因为您分配了新的内存并将其放置在szJobTitle中。&szJobTitle使用与&szName相同的内存位置,因此在第1行的赋值中,您会丢失在szName中分配的地址。如果szName和szJobTitle是不同类型的(具有不匹配的内存占用),设置szJobTitle也会破坏(或仅部分覆盖)szTitle。
正确的析构函数实现是什么?
我认为您没有足够的细节来实现析构函数。查看C++中带标记联合的概念以了解如何正确实现它。通常,您的联合成员应管理自己的内存(使用std::string,而不是char*),然后您的析构函数只会删除已分配的内容(但您必须显式调用它)。
同样地,如果我的类包含一个联合成员,如何销毁类对象(如何编写包括Union的类的析构函数)?

再看看区分联合类型。它基本上是联合和枚举的组合,其中枚举映射到联合的成员,并设置为指定已设置了联合的哪个成员。


1
你没有遵守new-delete配对的规则:newdelete配对,new[]delete[]配对。你正在使用new[],但是调用了delete,这是不兼容的。
另外,构造函数存在内存泄漏:一旦指针被赋值给szJobTitle,分配给szName的内存将永远不会被释放。
由于这是C ++,通常应该使用std::string而不是char*来处理字符串。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接