1

I am coming from a Java background. (Hash-) maps and sets are what I deal with daily in the course of my work. When it comes to C++ (or manually managed memory in general), I wonder how to work with them correctly.

By definition, when adding an object to a set / map, it will only be added if it / the identifier isn’t already present within. So the function calling Add() / Put() must not destroy the object after it was added (or a broken pointer would remain in the set), it must destroy it if it was not added because an equal object was found in the set (or a memory leak will be there), and it must not destroy it if an equal object was found in the set that happens to be the object that was tried to add. Creating a deep copy is not really possible (or I am not clear how) because the data objects may depend on each other recursively in my case.

When getting a subset from a set (lets say all bars from a set containing foos and bars), and the original set is deallocated, all foos have to be freeed, while bars contained in the subset must not be freeed. They must be freeed if the set gets freeed, but only if they are not any longer used in any other set. What if a set of long bars has been derived from the original set as well, and the set of bars does not know of, perhaps even in a different thread?

To illustrate the problem, here is a minimal example in Java to manage small records of linked data in memory. The main() method below shows some of its use cases possibilities.

interface IObject { }

interface INode extends IObject { }

interface IIdentifiable extends IObject {
    String m_csSubject();
}

class CLiteral implements IObject {
    private String m_csValue;
    CLiteral(String csValue) { m_csValue = csValue; }
    String m_csValue() { return m_csValue; }

    public int hashCode() {
        return 31 * 1 + ((m_csValue == null) ? 0 : m_csValue.hashCode());
    }

    public boolean equals(Object cObject) {
        if (this == cObject) return true;
        if (cObject == null) return false;
        if (getClass() != cObject.getClass()) return false;
        CLiteral cOther = (CLiteral) cObject;
        if (m_csValue == null) {
            if (cOther.m_csValue != null) return false;
        } else if (!m_csValue.equals(cOther.m_csValue)) return false;
        return true;
    }
}

class CLangString extends CLiteral {
    private final String m_csLanguage;
    CLangString(String csValue, String csLanguage) {
        super(csValue);
        m_csLanguage = csLanguage;
    }

    public int hashCode() {
        return 31 * super.hashCode() + ((m_csLanguage == null) ? 0 : m_csLanguage.hashCode());
    }

    public boolean equals(Object cObject) {
        if (this == cObject) return true;
        if (!super.equals(cObject)) return false;
        if (getClass() != cObject.getClass()) return false;
        CLangString cOther = (CLangString) cObject;
        if (m_csLanguage == null) {
            if (cOther.m_csLanguage != null) return false;
        } else if (!m_csLanguage.equals(cOther.m_csLanguage)) return false;
        return true;
    }
}

class CLink implements IIdentifiable, INode {
    private String m_csSubject;

    CLink(String csSubject) { m_csSubject = csSubject; }
    public String m_csSubject() { return m_csSubject; }

    public int hashCode() {
        return 31 * 1 + ((m_csSubject == null) ? 0 : m_csSubject.hashCode());
    }

    public boolean equals(Object cObject) {
        if (this == cObject) return true;
        if (cObject == null) return false;
        if (getClass() != cObject.getClass()) return false;
        CLink cOther = (CLink) cObject;
        if (m_csSubject == null) {
            if (cOther.m_csSubject != null) return false;
        } else if (!m_csSubject.equals(cOther.m_csSubject)) return false;
        return true;
    }
}

import java.util.*;
import java.util.Map.Entry;
class CNode implements INode {
    static final String m_csTYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
    final Map<String, Collection<IObject>> m_cEdges = new HashMap<>();

    CNode() { }
    CNode(CLink cType) { put(m_csTYPE, cType); }
    CNode(String csType) { this(new CLink(csType)); }

    CResult get(String csPredicate) {
        CResult cResult = new CResult();
        cResult.addAll(m_cEdges.get(csPredicate));
        return cResult;
    }

    CResult getById(String csPredicate, String csIdPredicate, String csIdValue) {
        CResult cResult = new CResult();
        for (Entry<String, Collection<IObject>> cEntry : m_cEdges.entrySet())
            for (IObject cValue : cEntry.getValue())
                if (cValue instanceof CNode) {
                    CNode cNode = (CNode) cValue;
                    if (cNode.hasType(csPredicate) && cNode.get(csIdPredicate).Strings().contains(csIdValue))
                        cResult.add(cNode);
                }
        return cResult;
    }

    boolean hasType(String csType) {
        for (IIdentifiable cIdentifiable : get(m_csTYPE).iIdentifiables())
            if (csType.equals(cIdentifiable.m_csSubject())) return true;
        return false;
    }

    <T extends CNode> T put(String csPredicate, IObject cObject) {
        Collection<IObject> cObjects = m_cEdges.get(csPredicate);
        if (cObjects == null) m_cEdges.put(csPredicate, cObjects = new HashSet<>());
        cObjects.add(cObject);
        return (T) this;
    }
}

class CNamedNode extends CNode implements IIdentifiable {
    private String m_csSubject;

    CNamedNode(String csSubject, CLink cType) {
        super(cType);
        m_csSubject = csSubject;
    }

    public String m_csSubject() { return m_csSubject; }

    public int hashCode() {
        return 31 * 1 + ((m_csSubject == null) ? 0 : m_csSubject.hashCode());
    }

    public boolean equals(Object cObject) {
        if (this == cObject) return true;
        if (cObject == null) return false;
        if (getClass() != cObject.getClass()) return false;
        CNamedNode cOther = (CNamedNode) cObject;
        if (m_csSubject == null) {
            if (cOther.m_csSubject != null) return false;
        } else if (!m_csSubject.equals(cOther.m_csSubject)) return false;
        return true;
    }
}

import java.util.*;
class CResult extends HashSet<IObject> {
    Set<CNamedNode> CNamedNodes() {
        Set<CNamedNode> cResult = new HashSet<>();
        for (IObject cMember : this)
            if (cMember instanceof CNamedNode) cResult.add((CNamedNode) cMember);
        return cResult;
    }

    Set<CNode> CNodes() {
        Set<CNode> cResult = new HashSet<>();
        for (IObject cMember : this)
            if (cMember instanceof CNode) cResult.add((CNode) cMember);
        return cResult;
    }

    Set<IIdentifiable> iIdentifiables() {
        Set<IIdentifiable> cResult = new HashSet<>();
        for (IObject cMember : this)
            if (cMember instanceof IIdentifiable) cResult.add((IIdentifiable) cMember);
        return cResult;
    }

    boolean IsAnyIdentifiable() {
        boolean bResult = false;
        for (IObject cMember : this) if (cMember instanceof IIdentifiable) return true;
        return bResult;
    }

    String String() {
        StringBuilder cResult = new StringBuilder();
        boolean bFirst = true;
        for (String csString : Strings()) {
            if(bFirst) bFirst = false; else cResult.append(", ");
            cResult.append(csString);
        }
        return cResult.toString();
    }

    Set<String> Strings() {
        Set<String> cResult = new HashSet<>();
        for (IObject cMember : this)
            if (cMember instanceof CLiteral) cResult.add(((CLiteral) cMember).m_csValue());
        return cResult;
    }
}

public static void main(String[] args) {
    final String m_csNS = "http://animalShelter.example/";
    CResult cDogs;
    CResult cFidos;
    {
        CNode cShelter = new CNode(m_csNS + "AnimalShelter");
        cShelter.put(
            m_csNS + "hasDog",
            new CNode(new CLink(m_csNS + "Dog"))
                .put(m_csNS + "hasName", new CLangString("Fido", "en"))
                .put(m_csNS + "residence", cShelter)
        );
        cShelter.put(
            m_csNS + "hasDog",
            new CNamedNode(
                "http://en.wikipedia.org/wiki/Scooby-Doo_(character)", new CLink(m_csNS + "Dog")
            )
                .put(m_csNS + "hasName", new CLangString("Scoobert \"Scooby\" Doo", "en"))
                .put(m_csNS + "residence", cShelter)
        );

        cDogs = cShelter.get(m_csNS + "hasDog");
        cFidos = cShelter.getById(m_csNS + "Dog", m_csNS + "hasName", "Fido");
    }

    if(!cDogs.isEmpty()){
        System.out.println("No. of dogs: " + cDogs.size());
    }
    if (cDogs.IsAnyIdentifiable()) {
        System.out.println("Famous dogs:");
        for (CNamedNode cWellKnownDog : cDogs.CNamedNodes()) {
            System.out.println("- " + cWellKnownDog.get(m_csNS + "hasName").String() + " ("
                    + cWellKnownDog.m_csSubject() + ')');
        }
    }

    if(!cFidos.isEmpty()){
        System.out.println(cFidos.CNodes().size() + " dog(s) called Fido.");
    }
}

How do I do this to C++? How do I implement object destruction inside my data model classes in a way that the function using it (main() in this case) has not (or the least possible) to care about the internals of the model?

Stanislav Kralin
  • 10,115
  • 4
  • 30
  • 52
Matthias Ronge
  • 7,765
  • 5
  • 38
  • 59
  • Are you talking about `std::unique_ptr` and `std::shared_ptr`? http://stackoverflow.com/questions/6876751/differences-between-unique-ptr-and-shared-ptr – Amadeus May 15 '17 at 14:23

1 Answers1

1

Inserting into C++ hash maps/sets (std::unordered_map and std::unordered_set) is done by making a copy. And that should be your default choice. Don't try to write C++ like you write Java.

If you want to avoid deep copies your map/set could contain raw-pointers to objects but the lifetime of the objects are maintained separately (in a std::vector for example).

However, there might be a reason to want more Java-like semantics. Perhaps, for example, you want a map/set to own a polymorphic type. In that case you probably want smart pointers. It is preferable to have unique ownership (there is only one smart pointer that owns each object) and in that case you can use std::unique_ptr or if you must have shared ownership use std::shared_ptr. Both kinds of smart pointer can be stored in a std::unordered_map or std::unordered_set and should have roughly the semantics you expect.

Chris Drew
  • 14,004
  • 3
  • 30
  • 51
  • Great answer. It led me to an [example](https://msdn.microsoft.com/de-de/library/hh279669.aspx#Beispiel) which is almost exactly what I need. – Matthias Ronge May 16 '17 at 06:26
  • @Paramaeleon No problem. Just to warn you, you may have to provide a custom hasher and equality predicate to the `std::unordered_set` you return in your `CResult` to get the behavior you want – Chris Drew May 16 '17 at 08:06