【问题标题】:emptying an std::queue through a function that returns the front() and pops()通过返回 front() 和 pops() 的函数清空 std::queue
【发布时间】:2015-08-13 23:25:31
【问题描述】:

我正在编写一个模板类,它应该提供一个防止并发访问的队列,基本上是为了能够编写可以处理具有可变数量的并发工作线程的项目列表的类。

模板类有一个方法:

bool getFront(T &value)
{ CritSectEx::Scope scope(listLock);
  bool ret = false;
    if( !itemList.empty() ){
        value = itemList.front();
        itemList.pop();
        ret = true;
    }
    return ret;
}

锁定队列,如果不为空则取出第一个元素,弹出并返回true,这样每个worker都可以做

    while( getFront(entry) ){
        // do something with entry
        nTreated += 1;
    }
    // exit worker

问题是:我通过value 引用返回这里到底是什么?当我测试这段代码时,我得到一个双重释放错误,因为我使用的类型 T 包含一个指针(我们称之为 B),该指针在其 dtor 中被删除(当 B 未标记为指向全局结构的指针时) )。该 dtor 已在 getFront() 中的 pop() 期间被调用,这似乎是合乎逻辑的。我注意到类型 T 的 ctor 在从现有实例创建新 T 时都分配了 B 的副本,并且它不会子类化任何可以提供“=”运算符的东西,所以我不确定我会如何结束2 个 T 实例,每个实例都包含相同的 B 值和“我们拥有 B”成员。

我不经常做这种事情,所以我可能在这里忽略了一些东西,但是什么?有什么建议可以解决这个问题吗?

补充观察:似乎总是从队列中弹出的最后一个元素显示此行为。

为了不那么模糊,这里是我使用的 typename T:

struct folder_info
{
    // plenty of members snipped
#ifdef __cplusplus
public:
    folder_info()
    {
        memset( this, 0, sizeof(struct folder_info) );
    }
    folder_info(const struct folder_info *src)
    {
        init(src);
    }
    folder_info(const struct folder_info &src)
    {
        init(&src);
    }
private:
    void init(const struct folder_info *src)
    {
        memcpy( this, src, sizeof(struct folder_info) );
        // we don't duplicate the filetypeslist!
        filetypeslist = NULL;
        filetypeslistlen = filetypeslistsize = 0;
    }
#endif
};

typedef struct FileEntry {
public:
    std::string fileName;
    struct stat fileInfo;
    FolderInfo *folderInfo;
    bool freeFolderInfo;
    FileEntry();
    FileEntry( const char *name, const struct stat *finfo, FolderInfo *dinfo, const bool ownInfo=false );
    FileEntry( const char *name, const struct stat *finfo, FolderInfo &dinfo );
    FileEntry(const FileEntry &ref);
    ~FileEntry();
} FileEntry;

FileEntry::FileEntry()
{
    folderInfo = NULL;
    freeFolderInfo = false;
}

FileEntry::FileEntry( const char *name, const struct stat *finfo, FolderInfo *dinfo, const bool ownInfo )
{
    fileName = name;
    fileInfo = *finfo;
    folderInfo = (ownInfo)? new FolderInfo(dinfo) : dinfo;
    freeFolderInfo = ownInfo;
}

FileEntry::FileEntry( const char *name, const struct stat *finfo, FolderInfo &dinfo )
{
    // creating a FileEntry with a FolderInfo reference always makes a copy
    // of the FolderInfo structure
    FileEntry( name, finfo, new FolderInfo(dinfo), true );
}

FileEntry::FileEntry(const FileEntry &ref)
{
    fileName = ref.fileName;
    fileInfo = ref.fileInfo;
    if( ref.freeFolderInfo ){
        folderInfo = new FolderInfo(ref.folderInfo);
    }
    else{
        folderInfo = ref.folderInfo;
    }
    freeFolderInfo = ref.freeFolderInfo;
}

FileEntry::~FileEntry()
{
    if( freeFolderInfo && folderInfo ){
        delete folderInfo;
        folderInfo = NULL;
    }
}

【问题讨论】:

  • 您能尝试创建一个Minimal, Complete, and Verifiable Example 并展示给我们看吗?请不要在 cmets 中发布重要信息(尤其不是代码),请编辑您的问题以包含此类内容。
  • 附加信息有帮助吗?我正在以一种可重用的方式编写此代码,但也已使用特定的应用程序(没有并行方面);提取有问题的代码并将其转换为独立示例需要一些工作。
  • 我认为@Joachim 指的是您发布的代码甚至无法编译,因此我们没有机会对其进行测试
  • 我对实际问题也有点不清楚。您是在问参数传递,还是关于双释放错误?
  • @JoachimPileborg 他可能有点困惑,正在错误的位置寻找错误的根源。我认为他的问题是分配而不是模板化的引用返回;使问题本质上与资源管理类中缺少分配运算符的一堆相关问题重复。

标签: c++ memory-management destructor


【解决方案1】:

如果我做对了,问题是缺少FileEntry 的用户定义赋值运算符。

如果复制的FileEntry 具有freeFolderInfo == true,则您的复制构造函数会处理每个FileEntry 具有新的FolderInfo 的事实,而您的赋值运算符则没有。

如果您将一个 FileEntry 分配给另一个 freeFolderInfo == true,则您将删除同一个指针两次,只要两者中的最后一个超出范围/被删除。

What is The Rule of Three?

如果您需要自己显式声明析构函数、复制构造函数或复制赋值运算符,您可能需要显式声明所有这三个。

【讨论】:

  • 不错的收获。无论如何,我都会推荐智能指针,而不是尝试为内部原始指针获取三个正确的规则。
  • @πάνταῥεῖ 我完全同意。
  • 零规则更好,除非所有权是类的唯一责任
  • 那么,我确实应该问是否定义了一个复制 ctor 而不是一个赋值运算符就足够了。事后看来,很明显我需要一个赋值运算符(以及更多的睡眠......),我正在 getFront() 中进行赋值。什么是零规则?如果链接正确,则智能指针是 C++11,因此对于这个特定的代码,我需要避免这种情况。此外,拥有和借用指针的区别是存在的,因为此代码与我不想完全重写的 C 代码接口(被调用)。而且总是复制 FolderInfo 结构会浪费内存。
  • 显然在添加赋值运算符后问题就消失了。我想我的测试没有在第一个带有自有指针的弹出队列元素上崩溃并没有帮助。
【解决方案2】:

“问题是:我通过值引用返回这里到底是什么?”

如果输出参数没有改变,调用者主要负责传入的值。虽然在完成任何操作之前分配一个适当的默认值可能会更好:

bool getFront(T &value)
{ 
  value = T(); // <<<<< Reset the output
//^^^^^^^^^^^^
  CritSectEx::Scope scope(listLock);
  bool ret = false;
    if( !itemList.empty() ){
        value = itemList.front();
        itemList.pop();
        ret = true;
    }
    return ret;
}

对于指针,这将导致nullptr 设置为value,这不会导致delete 出现问题。

您的双重删除问题很可能是因为在调用 getFront() 之前未正确初始化/重置输出值。


一般来说,我建议使用smart pointers 而不是原始指针,并尝试自行正确管理动态内存分配和删除。

使用像std::unique_ptr&lt;T&gt; 这样的类型存储在队列中应该可以顺利解决您的问题。

【讨论】:

  • 工人创建了一个本地 T (FileEntry) 实例,我认为这应该等同于在 getFront() 中执行value = T()。当 getFront() 返回 true 时,worker 也只对entry 执行某些操作。另外,我尝试使用 value = T(itemList.front()); 显式分配一个新实例(以及使用 T *value 的等效方法),但这并没有改变任何东西......
  • @RJVB 好吧,那么getFront() 似乎不是您的主要问题。您确定队列包含唯一的指针值吗?也只需使用smart pointers 而不是自己打扰delete。做错的可能性太高了,不值得。
  • 实际上,我确信我的队列至少包含 1 个重复的“拥有”指针值。管理的指针本身是正确的,我只是缺少一个赋值运算符
  • @RJVB 你应该听从我的建议,使用智能指针,让你的生活更轻松。
  • 如果这些是我理解的 C++11 特性,那么在这种特殊情况下我不能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-22
  • 1970-01-01
  • 1970-01-01
  • 2021-05-06
相关资源
最近更新 更多