FallenWarrior FallenWarrior - 11 months ago 53
C++ Question

Unexpected behavior when inserting values into a map using a loop and a function

I'm quite the beginner at C++ but I was trying to write a Rainmeter plugin which basically does the following:

  1. Scan all files in a directory set by Rainmeter options.

  2. Filter out all directories that aren't
    and only process those.

  3. Add some data to a
    , mostly calculated or fixed, with only the name directly influenced by the directory currently being processed.

  4. Write the whole thing to an ini-formatted file (that's the reason for using

For debug reasons, I used the console instead of an actual filestream. And that's how I noticed the problem. For some mysterious reasons, probably references expiring because of objects going out of scope, the final output is totally messed up.

First of all, it only consists of one entry, no matter what method I use to iterate over it. Now it gets weird. In two places it uses the name of the last found directory and in two other places it uses the name of the first found directory. And this output doesn't change no matter what I try. I already tried putting the code from inside the loop into a separate function and back, using pointers and explicit memory allocation with
literally everywhere I could and intentionally not calling
on those to try and keep them alive, and also using
at some places.

This is the current code used at the relevant part

typedef std::pair<const TCHAR*, const TCHAR*> kv_pair;
int elem_index = 1;
std::map<const TCHAR*, std::map<const TCHAR*, const TCHAR*>> content;
TCHAR[] measure_name = L"MeasureTest";
TCHAR[] dir = L"C:\\Foo\\Bar";
TCHAR[] searchPath = L"C:\\Foo\\Bar\\*"; //Of course these are actually dynamically sized

hFind = FindFirstFile(searchPath, &ffd);
//Error handling

do {
if((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (*ffd.cFileName != L'.')) {
StringCchCat(dir, wcslen(dir) + wcslen(ffd.cFileName) + 2, L"\\");
StringCchCat(dir, wcslen(dir) + wcslen(ffd.cFileName) + 2, ffd.cFileName);

const TCHAR* name = ffd.cFileName;
const TCHAR* measure_name = data->_measureName;

std::map<const TCHAR*, const TCHAR*>* tmp = new std::map<const TCHAR*, const TCHAR*>;

// BEGIN More or less irrelevant for the actual problem
int col = ((int) elem_index / 5) + 1;
int row = elem_index++ % 5;
TCHAR* style = new TCHAR[wcslen(L"PlaylistGeneral | PlaylistRow1 | PlaylistCol1") + 1];
StringCchPrintf(style, wcslen(L"PlaylistGeneral | PlaylistRow1 | PlaylistCol1") + 1, L"PlaylistGeneral | PlaylistRow%d | PlaylistCol%d", row, col);

TCHAR* lmuAction = new TCHAR[wcslen(L"[!CommandMeasure \"\"]") + wcslen(measure_name) + wcslen(path) + 1];
TCHAR* rmuAction = new TCHAR[wcslen(L"[\"\"]") + wcslen(path) + 1];
StringCchPrintf(lmuAction, wcslen(L"[!CommandMeasure \"\"]") + wcslen(measure_name) + wcslen(path) + 1, L"[!CommandMeasure %s \"%s\"]", measure_name, path);
StringCchPrintf(rmuAction, wcslen(L"[\"\"]") + wcslen(path) + 1, L"[\"%s\"]", path);
// END More or less irrelevant for the problem

tmp->insert(kv_pair(L"Meter", L"String"));
tmp->insert(kv_pair(L"Group", L"Playlist"));
tmp->insert(kv_pair(L"Hidden", L"1"));
tmp->insert(kv_pair(L"Text", name)); // These parts
tmp->insert(kv_pair(L"MeterStyle", style)); // are the
tmp->insert(kv_pair(L"LeftMouseUpAction", lmuAction)); // actual
tmp->insert(kv_pair(L"RightMouseUpAction", rmuAction)); // problem

content.insert(section(name, *tmp));
} while (FindNextFile(hFind, &ffd) && elem_index < 30);

//Some more error handling

for (const auto& section : content) {
std::wcout << '[' << current.first << ']' << std::endl;

for (const auto& kv : section.second) std::wcout << kv.first << '=' << kv.second << std::endl;

Running this with 3 folders being in
, named
, yields the following output

MeterStyle=PlaylistGeneral | PlaylistRow1 | PlaylistCol1
LeftMouseUpAction=[!CommandMeasure MeasureTest "C:\Foo\Bar\abc"]

As visible, the key holding
uses the value from the last iteration of the
each refer to their first values and only the last entry actually shows up.

Finally, I've reached the point of running out of ideas so I came here.

Answer Source

The reason for this behavior is const TCHAR* keys in your maps. As std::map compares key values to determine where to put data, it will not use the string name refers to;

Instead it will compare the pointer value itself - a memory address corresponding to ffd.cFileName, and that remains the same throughout the whole scope, which results in the single std::map entry.

Futhermore, std::map::insert does nothing when the key is already present in the map, which explains the fact that the data corresponds to the very first entry. As for the key itself, it always equals to ffd.cFileName, and as such the data it points to cycles through filenames and will stop at the last one.

The easiest (and correct) way to fix it is just as NathanOliver said - drop TCHAR*s altogether or at least in std::map etc, and use std::wstring instead.