1

I'm writing a template class that is supposed to provide a queue protected against concurrent access, basically to be able to write classes that can treat a list of items with a variable number of concurrent worker threads.

The template class has a method:

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

which locks the queue, and if it's not empty fetches the 1st element, pops it and returns true, so that each worker can do

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

The question is: what exactly am I returning here through the value reference? When I test this code, I get a double free error because the type T I'm using contains a pointer (let's call it B) that's deleted in its dtor (when B is not marked as being a pointer to a global structure). That dtor has been called during the pop() in getFront(), which seems logical. I took care that type T's ctors all allocate a copy of B when creating a new T from an existing instance and it does not subclass anything which could provide an '=' operator, so I'm not sure how I'd end up with 2 instances of T that each contain the same value of B and the "we own B" member.

I don't do this sort of thing often enough, so I'm probably overlooking something here, but what? Any suggestions how to troubleshoot this?

Additional observation: it appears that it's always the last element popped from the queue that shows this behaviour.

To make this less vague, here's what I use for 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;
    }
}
RJVB
  • 698
  • 8
  • 18
  • Can you please try to create a [Minimal, Complete, and Verifiable Example](http://stackoverflow.com/help/mcve) and show us? And please, don't post important information (especially not code) in comments, please edit your question to include such things instead. – Some programmer dude May 31 '15 at 12:54
  • Does the additional information help already? I *am* writing this in a way that it should be reusable, but also with a specific application already in place (without the parallel aspect); it'd take some work to extract the problematic code and convert it to a standalone example. – RJVB May 31 '15 at 13:04
  • I think what @Joachim is referring to is that the code you posted won't even compile , thus not giving us a chance to test it – Alejandro May 31 '15 at 13:13
  • I'm also a little unclear about the actual question. Are you asking about the argument passing, or about the double-free error? – Some programmer dude May 31 '15 at 13:19
  • @JoachimPileborg He's probably a little confused and is looking for the source of an error at the wrong spot. I assume that his problem is the assignment not the templated return by reference; making the question essentially a duplicate for a bunch of related questions missing assignment operators in resource-managing classes. – Pixelchemist May 31 '15 at 13:35
  • Indeed, I was a little confused shouldn't have been trying to whip up this code in between too many other things going on right now :) – RJVB May 31 '15 at 16:30

2 Answers2

2

In case I get it right, the problem is the lack of a user-defined assignment operator for FileEntry.

Your copy constructor takes care of the fact that each FileEntry has a new FolderInfo if the copied FileEntry has freeFolderInfo == true, whereas your assignment operator does not.

If you assign one FileEntry to another one and if both have freeFolderInfo == true, you'll delete the same pointer twice, as soon as the last of the two goes out of scope / is deleted.

What is The Rule of Three?

If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.

Community
  • 1
  • 1
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
  • Nice catch. I'd recommend smart pointers anyway, instead of trying to get the rule of three right for interned raw pointers. – πάντα ῥεῖ May 31 '15 at 13:42
  • @πάνταῥεῖ I totally agree. – Pixelchemist May 31 '15 at 13:44
  • rule of zero is better, unless ownership is the sole responsibility of the class – sp2danny May 31 '15 at 14:02
  • So, then I should indeed have asked if having defined a copy ctor and not an assignment operator was enough. With hindsight it's evident that I need an assignment operator (and more sleep...), I'm doing an assignment in getFront(). What's the rule of zero? Smart pointers are C++11 if the link is correct, and thus something I need to avoid for this particular code. Also, the distinction of owned and borrowed pointers is there because this code interfaces with (is called from) C code that I don't want to rewrite completely. And always copying the FolderInfo structure would be wasting memory. – RJVB May 31 '15 at 16:16
  • And evidently the issue went away after adding an assignment operator. I guess it didn't help that my test didn't crash on the 1st popped queue element with an owned pointer. – RJVB May 31 '15 at 16:32
1

"The question is: what exactly am I returning here through the value reference?"

Well if the output parameter isn't changed, the caller is primarily responsible for the value passed in. Though it might be better, to assign an appropriate default value before anything is done with it:

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;
}

For pointers, this will result in a nullptr set to value, which won't cause a problem with delete.

Your double delete problems are most probably because the output value wasn't properly initialized/reset before calling getFront().


In general I would recommend to use smart pointers instead of raw pointers, and trying to manage dynamic memory allocation and deletion correctly on your own.

Using something like a type as std::unique_ptr<T> to be stored in your queue should solve your problems smoothly.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • The worker creates a local T (FileEntry) instance which should be equivalent to doing `value = T()` in getFront() I think. The worker also only does something with `entry` when getFront() returned true. Also, I tried doing `value = T(itemList.front());` to allocate a new instance explicitly (and an equivalent approach using T *value instead), and that didn't change anything... – RJVB May 31 '15 at 13:07
  • @RJVB Well, then `getFront()` doesn't seem to be your primary problem. Are you sure the queue contains unique pointer values? Also just use [smart pointers](http://en.cppreference.com/w/cpp/memory) instead of bothering with `delete` yourself. The probability to do it wrong is simply to high and not worth it. – πάντα ῥεῖ May 31 '15 at 13:09
  • Actually, I was sure that my queue contained at least 1 duplicate "owned" pointer value. The pointer managed was correct in itself, I was just missing an assignment operator – RJVB May 31 '15 at 17:34
  • @RJVB You should actually follow my advice to use smart pointers, to ease your life. – πάντα ῥεῖ May 31 '15 at 18:05
  • If those are a C++11 feature as I understand they are, I can't in this particular case. – RJVB May 31 '15 at 19:23