I have 300+ classes. They are related in some ways.
For simplicity, all relation are 1:1.
Here is a sample diagram.
(In real case, there are aroun
You could have each class contain a vector of strings that they accept upon creation of the class if the associations are known ahead of time. You could also add an update method that would update this container of names if more are discovered later. If the update function to update the list has been called and the class's container of names has been changed then the function also needs to have the class update itself with the proper class associations or relationships.
Each class would need these elements at minimum and for storing different types into a single container will require the use of a common non functional abstract base class with some purely virtual methods.
I'm using 2 classes that are derived from a common base interface for my example and the Primary class is named to represent the class that is having the relationship assigned to where the Associate class is the class is the delegated class that is being assigned to give the primary class that link of association.
class Base {
protected:
std::vector vRelationshipNames_;
std::vector vRelationships_;
public:
Base(){}
virtual ~Base(){}
virtual void updateListOfNames( std::vector newNames );
};
class Primary : Base {
private:
std::string objectName_;
public:
// Constructor if relationships are not known at time of instantiation.
explicit Primary( const std::string& name );
// Constructor if some or all relationships are known. If more are discovered then the update function can be used.
Primary( const std::string& name, std::vector relationshipNames );
// Add by const reference
void add( const Base& obj );
// Remove by const reference or by string name.
void remove( const Base& obj );
void remove( const std::string& name );
// If needed you can even override the update method.
virtual void updateListOfNames( std::vector newNames ) override;
};
// Would basically have similar fields and methods as the class above, stripped them out for simplicity.
class Associate : Base {
std::string objectName_;
};
We can then use a function template that takes two class objects to search to see if the Associate Object is in the list of names of the Primary Object
template
T& setRelationshipBetweenClasses( class T& primaryObject, class U& associateObject ) {
// Search through primaryObject's list of names to see if associate class is listed
// If it is not then return from function otherwise, we need to search to see
// if this class was already added to its list of shared pointers.
// If it is not found then add it by calling the Primary's add function
// Then we also need to call the Associates add function as well by
// passing it a const reference to the Primary class this way both
// classes now have that relationship.
// we also return back the reference of the changed Primary object.
}
EDIT
The OP made a comment about using string and being slow; I used string here in the pseudo code just for clarity of understanding, you can replace the std::string
with an unsigned int
and just use a numeric ID. It will do the same and should be fairly efficient.
EDIT
For the OP -
A common interface of classes without definitions and implementations but their declarations might look something like this:
Example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H
struct CommonProperties {
std::string name_;
unsigned int id_;
// Default
explicit CommonProperties() : name_(std::string()), id_(counter_) {
counter_++;
}
// Passed In Name
explicit CommonProperties(const std::string& name) : name_(name), id_(counter_) {
counter_++;
}
private:
static unsigned int counter_;
};
class BaseObject {
protected:
CommonProperties properties_;
// Sizes of Both Containers Should Always Match!
std::vector> sharedObjects_; // Container of Shared Names
std::vector sharedObjectIDs_; // Container of Shared IDs
public:
explicit BaseObject(const std::string& strName) {
properties_.name_ = strName;
}
// Virtual Interface for Abstract Base Class
virtual void add(const BaseObject& obj, const std::string& strName, const unsigned int id) = 0; // Purely Virtual Each Derived Class Must Implement
virtual void update(const BaseObject& obj, const std::string& strName, const unsigned int id) = 0; // Also purely virtual
virtual void remove(const std::string& strName) {} // Used string method to remove
virtual void remove(const unsigned int id) {} // Use ID method to remove
// Get Containers
std::vector> getObjects() const { return sharedObjects_; }
std::vector getIDs() const { return sharedObjectIDs_; }
};
class Primary : public BaseObject {
// Member Variables
public:
protected:
private:
// Constructors, Destructor and Methods or Functions
public:
explicit Primary(const std::string& strName) : BaseObject(strName) {
}
// Must Have Purely Virtual
void add(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
// Algorithm Here
}
void update(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
// Algorithm Here
}
// other public methods;
protected:
private:
};
class Associate : public BaseObject {
// Member Variables:
public:
protected:
private:
// Constructors, Destructors and Methods or Functions
public:
explicit Associate(const std::string& strName) : BaseObject(strName) {
}
// Must Have Purely Virtual
void add(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
// Algorithm Here
}
void update(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
// Algorithm Here
}
protected:
private:
};
#endif // EXAMPLE_H
Example.cpp
#include "stdafx.h" // Used for common std containers and algorithms as well as OS and system file includes.
#include "Example.h"
unsigned int CommonProperties::counter_ = 0x00;
With this example I have both a string and ID. I do this for several reasons; if you ever need to write to a readable file, or to print to the screen or some other output device the contents or properties of this object I have string for human readability. The ability to search, remove and add by a string is available but for the sake of efficiency it should be done by the mechanics of the hidden engine that will instead automatically generate the IDs for you and use the ID system instead for faster searches, comparisons and removals.
For example let's say I generated 3 distinct objects that are of different classes: class1, class2, class3
their names and id's are set upon creation. I didn't show how to automatically generate a unique string with a base set of characters for a specific class and then append to that string a unique value each time that class is instantiated, but that is what I typically do if a name is not supplied. Supplying a name is optional and name generation is normally automatic. The table of the different classes and their properties name field would look like this:
// CLASS NAME | ID
"class1" | 0x01
"class2" | 0x02
"class3" | 0x03
Now what also makes this setup powerful is the fact that you can have multiple instances of the same class but each has their own unique name. Such as This
class PickupTruck {};
// Table of Names & IDS similar to above:
"Chevy" | 0x04
"Dodge" | 0x05
"Ford" | 0x06
"GMC" | 0x07
Now if you want to distinguish between the name of the actually class and the name of the actual object description; just make sure that you add a std::string
as a protected member to the Base
or Super class that these classes derive from. This way that name would represent the string representation of that class type, where the property sheet name would be the actual descriptive name of that object. But when doing the actual searches and removals from your containers, using ids for simple loops, counters and indexing are quite efficient.