如何避免 C++ 创建给定类实例的额外副本?

问题描述 投票:0回答:1

我有一个

Child
类,它保存对外部管理内存的引用(在这种特殊情况下,Vulkan 对象是通过 Vulkan 调用创建/删除的,而不是通过标准 C++ 内存管理生命周期)。为了再现的目的,在下面的示例中通过手动分配内存来举例说明。我仍然想依靠 RAII 来处理我的 C++ 对象并在
Child
的析构函数上释放外部分配的内存。在我的实际代码中,
Child
类中有多个
Parent
实例,因此我也将其包含在此处以完成。这是 MRE:

#include <cstdlib>
#include <memory>
#include <iostream>

class Child {
    public:
        Child(void * memoryRegion): mMemoryRegion(memoryRegion){}
        ~Child(){
            std::cout << "Destroying" << std::endl;
            free(mMemoryRegion);
        }
        Child(const Child&) = delete;
        Child(Child&&) = default;
        Child& operator=(const Child&) = delete;
        Child& operator=(Child&&) = default;

    private:
        void * mMemoryRegion;
};

Child createChild(){
    void * memory = malloc(100);
    return Child(memory);
}


class Parent {
    public:
        Parent (Child &c1) : child1(std::move(c1)){}
    private:
        Child child1;
};

std::unique_ptr<Parent> createParent(){
    Child c1 = createChild();
    return std::make_unique<Parent>(c1);
}

int main(){
    auto parent = createParent();
    return 0;
}

如您所见,我尝试禁用复制构造函数,并使用

std::move
语义,但这仍然会导致崩溃:

Destroying
Destroying
free(): double free detected in tcache 2
[1]    3568506 IOT instruction  ./main

我确信这是我的移动语义上的一个愚蠢的错误,但我一直无法弄清楚。这样做的正确方法是什么?我可以尝试使用

std::unique_ptr
,我很确定这可以解决问题,但我仍然很好奇是否可以在没有它的情况下解决这个问题。

c++ c++11
1个回答
0
投票
        ~Child(){
            std::cout << "Destroying" << std::endl;
            free(mMemoryRegion);
        }

如果这个对象在某个时刻仍然被移动,它的析构函数仍然会执行。

因此,当这个对象从 before all 移动并说完时,两个对象将被销毁,并且将有两个析构函数都尝试

free
同一个指针。再见。

移动语义不要改变核心C++基本原则:构造的每个对象都将被破坏(在格式良好的C++程序中,涉及

exit()
的各种边缘情况对于本次讨论的目的来说并不重要)。

一次移动只是一次“高效”的复制。就这样。当对象移动到类的新实例时,您无法“避免”创建“额外副本”。 C++ 不是这样工作的。您现在有了一个新对象。移出的对象仍然存在。它的析构函数仍然会在某个时刻执行。

您需要考虑到这一点来设计您的课程。

您不能在此处使用默认的移动构造函数。您的移动构造函数必须复制移出对象的指针,就像默认构造函数一样,但然后将移出对象的指针设置为

nullptr

您的移动赋值运算符必须

free
移至对象的现有指针,如果其不为空,则复制移出对象的指针,然后将其设置为
nullptr

你的析构函数应该调整为

free
只是一个非
nullptr
指针,迂腐。

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