有人可以解释这个C ++联合示例吗?

问题描述 投票:3回答:2

我在cppreference.com上找到了这段代码。这是我见过的最奇怪的C ++,我有几个问题:

union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {}  
};          

int main()
{
    S s = { "Hello, world" };
    // at this point, reading from s.vec is undefined behavior
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string<char>();
    new (&s.vec) std::vector<int>;
    // now, s.vec is the active member of the union
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector<int>();
}

我想确保我有一些正确的事情。

  1. 联合强制您通过删除默认构造函数来初始化其中一个联合成员,在这种情况下,他使用Hello World初始化该字符串。
  2. 在他初始化字符串后,矢量在技术上还不存在?我可以访问它,但它还没有构建?
  3. 他通过调用析构函数显式地破坏了字符串对象。在这种情况下,当S超出范围时,是否会调用~S()析构函数?如果是这样,在哪个对象上?如果他没有在字符串上显式调用析构函数,那是内存泄漏吗?我倾向于不,因为字符串清理自己,但对于工会,我不知道。他自己调用了字符串和向量的析构函数,所以~S()析构函数似乎没用,但是当我删除它时,我的编译器不会让我编译它。
  4. 这是我第一次看到有人使用new运算符将对象放在堆栈上。在这种情况下,这是现在可以使用向量的唯一方法吗?
  5. 当你使用向量new时,你不应该在它上面调用delete,因为还没有分配新的内存。通常,如果你在堆上放置new,你必须释放()内存以避免泄漏,但在这种情况下,如果他让vector和union超出范围而不调用析构函数会发生什么?

我发现这真的令人困惑。

c++ class destructor unions
2个回答
7
投票
  1. 对,就是这样。
  2. 因为向量和字符串使用相同的底层存储(这是unions的工作方式),并且该存储当前包含一个字符串,所以没有放置的位置,并且尝试访问它将是未定义的。并不是说它还没有建成;它是无法构造的,因为路上有一个字符串。
  3. 每当S超出范围时,它的析构函数就会被调用。在这种情况下,这是union的析构函数,它被明确定义为什么都不做(因为union不能知道哪个成员是活动的,所以它实际上不能做它应该做的事情)。因为联合不能知道它的哪个成员是活动的,如果你没有显式调用字符串的析构函数,它就不能知道那里有一个字符串而且字符串不会被清除。当工会成员有非平凡的析构函数时,编译器会让你编写自己的析构函数,因为它不知道如何清理它并希望你这样做;在这个例子中你不知道如何清理它,所以你在union的析构函数中什么都不做,并让使用S的人手动调用正确元素上的析构函数。
  4. 这称为“placement new”,是在现有内存位置构造对象而不是分配新对象的典型方法。除了工会之外,还有它的用途,但我相信这是在不使用未定义的行为的情况下将矢量引入此并集的唯一方法。
  5. 正如第3部分所述,当s超出范围时,它不知道它是否包含字符串或向量。 ~S析构函数不执行任何操作,因此您需要使用自己的析构函数来销毁向量,就像使用字符串一样。

要查看为什么联合不能自动知道要调用哪个析构函数,请考虑以下替代函数:

int maybe_string() {
    S s = {"Hello, world"};
    bool b;
    std::cin >> b;
    if (b) {
        s.str.~basic_string<char>();
        new (&s.vec) std::vector<int>;
    }
    b = false;
    // Now there is no more information in the program for what destructor to call.
}

在函数结束时,编译器无法知道s是否包含字符串或向量。如果你没有手动调用析构函数(假设你有办法告诉我,我认为你没有在这里做),它必须安全地发挥它并且不会破坏任何一个成员。 C ++的创建者决定保持简单并且永远不会自动破坏联合的活动成员而不是强制执行,而不是关于编译器何时能够销毁活动成员以及什么时候不会破坏任何东西的复杂规​​则。程序员手动完成。


3
投票

联合强制您通过删除默认构造函数来初始化其中一个联合成员,在这种情况下,他使用Hello World初始化该字符串。

正确

在他初始化字符串后,矢量在技术上还不存在?我可以访问它,但它还没有构建?

好吧,即使它是可访问的并不意味着你可以访问。由于它不是访问它的活动项,因此是未定义的行为。原因是它的生命周期尚未开始,因为它的构造函数尚未被调用。

会调用~S()析构函数吗?

不,s只有在超出范围时才会被销毁。

如果他没有在字符串上显式调用析构函数,那是内存泄漏吗?

是的,但实际上它是未定义的行为。因为析构函数不是微不足道的,所以不能在不破坏活动成员的情况下更改成员。如果你没有在创建向量之前销毁字符串,那么你将失去包含它所持有的内存的字符串的状态(如果它保持任何 - 请参阅small string optimizations如何不能)。

所以~S()析构函数似乎没用,但是当我删除它时,我的编译器不会让我编译它。

正如你所说的那样没用,但你真的可以做到。联合必须有一个析构函数,并且编译器提供了一个被删除,因为std::stringstd::vector有非平凡的析构函数。

在这种情况下,这是现在可以使用向量的唯一方法吗?

是。您必须使用placement new才能构造对象。如果你没有,并试图做类似的事情

s.vec = std::vector<int>{};

然后,您将分配一个从未构造过的未定义行为的对象。

向量和联合超出范围而不调用析构函数?

好吧,如果他们没有手动破坏矢量,那么你会泄漏矢量所持有的东西,因为什么都不会被破坏。只要在联盟超出范围之前销毁活动成员,那么你就可以了。

© www.soinside.com 2019 - 2024. All rights reserved.