【问题标题】:How to best handle copy-swap idiom with uninitialised memory如何最好地处理未初始化内存的复制交换习语
【发布时间】:2015-12-02 23:47:57
【问题描述】:

作为一项学术练习,我创建了一个自定义矢量实现,我希望支持复制非 pod 类型。

我希望容器支持存储不提供默认构造函数的元素。

当我为向量保留内存,然后 push_back 一个元素(它管理它自己的资源并实现了复制和赋值运算符 - 我暂时忽略了移动构造函数)时,我在使用复制交换时遇到了问题该类型的成语。

因为交换发生在仍然是未初始化内存的类型上,所以在交换之后,临时调用的析构函数将尝试释放一些未初始化的数据,这当然会爆炸。

我可以看到一些可能的解决方案。一种是确保所有非 pod 类型都实现一个默认构造函数,并在集合中的每个元素上调用它(放置 new)。我不喜欢这个想法,因为它看起来既浪费又麻烦。

另一种方法是在进行交换之前将容器中类型空间的内存设置为 0(这样临时将为 null 并且调用析构函数将正常运行)。不过,这对我来说有点 hacky,我不确定是否有更好的选择(请参阅下面的代码以获取此示例)您也可以在为一堆元素调用 reserve 之后将所有保留空间 memset 为 0 但是这又是一种浪费。

是否有关于如何为 std::vector 实现这一点的文档,因为调用 reserve 不会为分配的元素调用构造函数,而 resize 会(对于不实现默认构造函数的类型,可以将构造的临时变量作为第二个参数传递接电话)

下面是一些你可以运行来演示问题的代码,我省略了实际的向量代码,但原理保持不变。

#include <iostream>
#include <cstring>

// Dumb example type - not something to ever use
class CustomType {
public:
    CustomType(const char* info) {
        size_t len = strlen(info) + 1;
        info_ = new char[len];
        for (int i = 0; i < len; ++i) {
            info_[i] = info[i];
        }
    }

    CustomType(const CustomType& customType) {
        size_t len = strlen(customType.info_) + 1;
        info_ = new char[len];
        for (int i = 0; i < len; ++i) {
            info_[i] = customType.info_[i];
        }
    }

    CustomType& operator=(CustomType customType) {
        swap(*this, customType);
        return *this;
    }

    void swap(CustomType& lhs, CustomType& rhs) {
        std::swap(lhs.info_, rhs.info_);
    }

    ~CustomType() {
        delete[] info_;
    }

    char* info_;
};

int main() {
    CustomType customTypeToCopy("Test");

    // Mimics one element in the array - uninitialised memory
    char* mem = (char*)malloc(sizeof(CustomType));

    // Cast to correct type (would be T for array element)
    CustomType* customType = (CustomType*)mem;  
    // If memory is cleared, delete[] of null has no effect - all good
    memset(mem, 0, sizeof(CustomType));
    // If the above line is commented out, you get malloc error - pointer 
    // being freed, was not allocated

    // Invokes assignment operator and copy/swap idiom
    *customType = customTypeToCopy;

    printf("%s\n", customType->info_);
    printf("%s\n", customTypeToCopy.info_);

    return 0;
}

任何信息/建议将不胜感激!

解决了!

感谢@Brian 和@Nim 帮助我了解分配(复制/交换)何时有效的用例。

为了实现我想要的,我只需要替换行

*customType = customTypeToCopy;

new (customType) CustomType(customTypeToCopy);

调用复制构造函数而不是赋值运算符!

谢谢!

【问题讨论】:

  • 我已经读过这个问题几次(可能已经晚了),但不清楚您是否在为 custom type矢量? (顺便说一句。我不认为memset() 会起作用)并且尚不清楚具有统一化内存的复制交换在哪里启动(意味着您正在尝试访问向量“边界”之外的元素.. .)
  • 嘿尼姆!对不起,如果我的解释不是很好,我会尽力澄清。基本上在进行交换时,位于未初始化内存值中的 info_ 指针被移动到我传递给该函数的临时对象中,然后当范围结束时,在该临时对象上调用析构函数,但 info_ 指针只是垃圾,因此调用 delete[] 会导致崩溃。如果我之前memset内存,它会为null,调用delete没有效果。我只是想知道这是一件好事还是不好的做法。我想不出一个好的选择:(
  • ...但这意味着您正在 swapping 与一个 out-of-bounds 的元素(即超出 size 的向量..)
  • 不,我已经为它分配了空间(参见示例代码)它只是未初始化,所以任何东西都可能在那个空间中,因为没有调用构造函数并且我没有将内存归零(除非我打电话给memset)。您可以将代码复制并粘贴到 ideone 中,看看它在做什么,如果您注释掉 memset 行会发生什么,如果有帮助的话。
  • ..我理解-但是您正在建模的是您正在与尚未构造的对象交换的情况-这是无效的。您不能与尚未构造的对象交换(在向量的上下文中,您正在访问其边界之外的元素 - 即尚未构造的元素 - 保留存储不一样作为构造一个对象...)

标签: c++ c++11 memory vector deep-copy


【解决方案1】:

你不使用复制和交换来构建。

为了解决以下问题,您使用复制和交换进行分配:分配的左侧是一个已经初始化的对象,因此它需要在复制右侧的状态之前释放它所拥有的资源或搬进去;但是如果复制或移动构造因抛出异常而失败,我们希望保持原始状态。

如果你在做构造而不是赋值——因为目标未初始化——复制和交换解决的问题不存在。您只需使用placement new 调用构造函数。如果成功,那就太好了。如果它通过抛出异常而失败,该语言保证已经构造的任何子对象都被销毁,并且您只需让异常向上传播;在失败的情况下,目标的状态将与之前相同:未初始化。

【讨论】:

  • 啊,好吧,我明白了——所以我的问题变成了如果该对象没有默认构造函数怎么办?我认为我需要在该元素上调用placement new,但是如果我想调用特定的构造函数怎么办。有没有办法做到这一点?当您在 std::vector 上调用 reserve 然后 push_back 元素时,std::vector 如何处理这种情况?非常感谢您的回答!
  • @Tom vector::push_back 使用复制或移动构造函数。如果类型不能被复制或移动,你就不能拥有它的向量(或者至少你不能push_back 进入它)。
  • 啊,好的,我明白了,谢谢布赖恩!我有我的解决方案! :) 如果我理解正确,我只需要更新代码,而不是作为分配我这样做 - new (customType) CustomType(customTypeToCopy);非常感谢你!! :)
  • @Tom 您可能还想为push_back 添加右值重载并使用std::move
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-28
  • 2011-01-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
相关资源
最近更新 更多