Click here to Skip to main content
15,893,161 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi,

As part of a personal project with Unreal Engine 4, I'm learning c++. After much deliberation I think I'm finally starting to understand some new concepts which you simply don't see with managed languages. My goals here are as follows;

1. To manage all memory properly to avoid any memory leaks
2. To have all code as compact as possible while not being ugly
3. To abide by c++ standards as much as possible

If you could point out any changes you'd make to this code I've written, please let me know. Once I know how I should be writing the code properly, I can confidently apply that to the rest of my project as I write it.

This class is called ContainerAttributes, and is effectively a dictionary of names and values, for example capacity is 100, health is 75 etc. As different objects will have different storage information, eg. some items may be invincible and may have no health attributes, I'm not hardcoding anything in, rather I'm allowing the programmer to specify the attributes and values dynamically.

.h:
C++
#pragma once

#include "DebugHelper.h"

#include "CoreMinimal.h"

class NOCTURNALSYNERGY_API ContainerAttributes
{
public:
    ContainerAttributes();
    ~ContainerAttributes();
	void SetValue(string name, int value);
    int GetValue(string name);

    void Debug();

private:
    map<string, int> values;

};


As I mentioned, I have no experience with memory management but I understand that anything created with 'new' returns a pointer to the object which must be deleted to free memory using 'delete'. To the best of my knowledge as well, ~ methods are deconstructors, which would come about in the following situation;

C++
obj->containerAttributes = new ContainerAttributes();
obj->whatever();
delete obj;

(then inside obj)
obj::~obj()
{
delete containerAttributes;
}


This should consequently call the deconstructor in ContainerAttributes which can free its memory (the values map).

.cpp:
C++
#include "ContainerAttributes.h"

ContainerAttributes::ContainerAttributes()
{
    values = new map<string, int> {};
}

ContainerAttributes::~ContainerAttributes()
{
    delete values;
}

void ContainerAttributes::SetValue(string name, int value)
{
    auto it = values.find(name);

    if(it != values.end())
    {
        values[name] = value;
    }
    else
    {
        values.insert(pair<string, int>(name, value));
    }
}

int ContainerAttributes::GetValue(string name)
{
    auto it = values.find(name);

    if(it != values.end())
    {
        return it->second;
    }
    else
    {
        DebugHelper::Debug("Error in ContainerAttributes; map does not contain name '" + name + "'");
    }
}

void ContainerAttributes::Debug()
{
    map<string, int>::iterator it;

    for(it = values.begin(); it != values.end(); it++)
    {
        DebugHelper::Debug(it->first + ": " + it->second);
    }
}


A few other questions regarding this, DebugHelper::Debug accepts an FString. Is it even possible to concatenate strings in this way using '+' or do I have to use another method? I'm happy to create an additional method to accept std::string as well, but I'm not sure whether '+' will work there either.

Finally, is it best to use
auto it = values.find(name);

or should I explicitly specify something like this?
pair<string, int> it = values.find(name);


Thanks for any help you can give.

What I have tried:

--------------------------------------------------------------------------
Posted
Updated 22-May-18 2:43am
v6

1 solution

If you avoid using allocated member variables, the destructor of members is called automatically reducing the chance of having memory leaks. With your obj example it looks like you can do that by changing the member from *ContainerAttributes to ContainerAttributes.

You might also think about making your ContainerAttributes class std::map based:
class NOCTURNALSYNERGY_API ContainerAttributes : public std::map<std::string, int>
{
public:
    ContainerAttributes();
    ~ContainerAttributes();
    void SetValue(const std::string& name, int value)
    {
        auto it = find(name);
        if (it != end())
            it->second = value;
        else
            insert(std::pair<std::string, int>(name, value));
    }
    int GetValue(const std::string& name) const
    {
        const auto it = find(name);
        return it == end() ? -1 : it->second;
    }
};
However, this will make access to the map public. Note also that I have changed the name parameters to be const and passed by reference which is the appropriate passing here.

According to the FString | Unreal Engine[^] documentation it provides a += operator (which probably is just calling Append() internally). So you can use the + operator. But in your case
DebugHelper::Debug(it->first + ": " + it->second);
the operation is performed on (and as) std::string and not on FString.


When to use auto and when not is your decision and commonly choosen depending on the context. Just search the web for something like "c++ auto or not". A probably good read: Don't use <auto> unless you mean it — Joseph Mansfield[^].
 
Share this answer
 
Comments
[no name] 22-May-18 8:47am    
Very useful, thank you. Just a few things, what do you mean by changing the member from *ContainerAttributes to ContainerAttributes?

Also, is the idea of 'const auto it' mainly to display intent, which is possible because it->second isn't being modified, only read?
Jochen Arndt 22-May-18 9:09am    
The member of your obj class is actually a pointer (using new). But if you have only a single instance (not an array), there is no need to use a pointer / allocated instance. Just use a single instance as member:
class obj
{
// ...
// was:
//ContainerAttributes* containerAttributes;
// better:
ContainerAttributes containerAttributes;
};

Then the constructor is called from within the obj constructor. And similar for the destructor. No need to call
delete containerAttributes;
in the obj destructor. The ContainerAttributes destructor is called automatically deleting the std::map.

const instructs the compiler to not allow changing the object. It should be always used when an object is not changed.

[EDIT]
In this special case it is not really necessary to use const for the iterator because I have made the function GetValue() const. As a result, the compiler will automatically choose the find() overload that returns a const iterator when using auto. So it is a good example for an advantage of using auto (within const member functions).
[/EDIT]
CPallini 22-May-18 9:11am    
What about

void SetValue(const std::string& name, int value)
{
this->operator[](name) = value;
}

?
Jochen Arndt 22-May-18 9:17am    
My virtual 5!
[no name] 22-May-18 10:01am    
CPallini, why do you use const in that situation for the std::string but not for the int?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900