10.4 — Association

In the previous two lessons, we’ve looked at two types of object composition, composition and aggregation. Object composition is used to model relationships where a complex object is built from one or more simpler objects (parts).

In this lesson, we’ll take a look at a weaker type of relationship between two otherwise unrelated objects, called an association. Unlike object composition relationships, in an association, there is no implied whole/part relationship.


To qualify as an association, an object and another object must have the following relationship:

  • The associated object (member) is otherwise unrelated to the object (class)
  • The associated object (member) can belong to more than one object (class) at a time
  • The associated object (member) does not have its existence managed by the object (class)
  • The associated object (member) may or may not know about the existence of the object (class)

Unlike a composition or aggregation, where the part is a part of the whole object, in an association, the associated object is otherwise unrelated to the object. Just like an aggregation, the associated object can belong to multiple objects simultaneously, and isn’t managed by those objects. However, unlike an aggregation, where the relationship is always unidirectional, in an association, the relationship may be unidirectional or bidirectional (where the two objects are aware of each other).

The relationship between doctors and patients is a great example of an association. The doctor clearly has a relationship with his patients, but conceptually it’s not a part/whole (object composition) relationship. A doctor can see many patients in a day, and a patient can see many doctors (perhaps they want a second opinion, or they are visiting different types of doctors). Neither of the object’s lifespans are tied to the other.

We can say that association models as “uses-a” relationship. The doctor “uses” the patient (to earn income). The patient uses the doctor (for whatever health purposes they need).

Implementing associations

Because associations are a broad type of relationship, they can be implemented in many different ways. However, most often, associations are implemented using pointers, where the object points at the associated object.

In this example, we’ll implement a bi-directional Doctor/Patient relationship, since it makes sense for the Doctors to know who their Patients are, and vice-versa.

This prints:

James is seeing patients: Dave
Scott is seeing patients: Dave Betsy
Dave is seeing doctors: James Scott
Frank has no doctors right now
Betsy is seeing doctors: Scott

In general, you should avoid bidirectional associations if a unidirectional one will do, as they add complexity and tend to be harder to write without making errors.

Reflexive association

Sometimes objects may have a relationship with other objects of the same type. This is called a reflexive association. A good example of a reflexive association is the relationship between a university course and its prerequisites (which are also university courses).

Consider the simplified case where a Course can only have one prerequisite. We can do something like this:

This can lead to a chain of associations (a course has a prerequisite, which has a prerequisite, etc…)

Associations can be indirect

In all of the previous cases, we’ve used either pointers or references to directly link objects together. However, in an association, this is not strictly required. Any kind of data that allows you to link two objects together suffices. In the following example, we show how a Driver class can have a unidirectional association with a Car without actually including a Car pointer or reference member:

In the above example, we have a CarLot holding our cars. The Driver, who needs a car, doesn’t have a pointer to his Car -- instead, he has the ID of the car, which we can use to get the Car from the CarLot when we need it.

In this particular example, doing things this way is kind of silly, since getting the Car out of the CarLot requires an inefficient lookup (a pointer connecting the two is much faster). However, there are advantages to referencing things by a unique ID instead of a pointer. For example, you can reference things that are not currently in memory (maybe they’re in a file, or in a database, and can be loaded on demand). Also, pointers can take 4 or 8 bytes -- if space is at a premium and the number of unique objects is fairly low, referencing them by an 8-bit or 16-bit integer can save lots of memory.

Composition vs aggregation vs association summary

Here’s a summary table to help you remember the difference between composition, aggregation, and association:

Property Composition Aggregation Association
Relationship type Whole/part Whole/part Otherwise unrelated
Members can belong to multiple classes No Yes Yes
Members existence managed by class Yes No No
Directionality Unidirectional Unidirectional Unidirectional or bidirectional
Relationship verb Part-of Has-a Uses-a

10.5 -- Dependencies
10.3 -- Aggregation

116 comments to 10.4 — Association

  • Ritesh

    Hey Alex,
    Aren't unidirectional associations similar to aggregation.
    How can be differentiate them.

    • Alex

      The parent in an aggregation owns the relationship with the child. If the parent dies, the children die too. In an association, this isn't true.

  • Roy

    in the doctor patient example, if we delete doctor james, then let the program to print doctor-patient relation, the output will still be correct?

    • Alex

      No, if you delete the Doctor and then try to print the Doctor-Patient relationship, the program will exhibit undefined behavior, because you'll be accessing memory that has been deleted.

      When the Doctor is deleted, you also need to ensure that all pointers to the Doctor are removed. This can be a bit of a management challenge. Fortunately, C++ provides a class that can help with this: std::shared_ptr, which we cover in chapter 15.

  • Maxpro

    "In the above example, we have a CarLot holding our cars. The Driver, who needs a car, doesn’t have a pointer to his Car -- instead, he has the ID of the car, which we can use to get the Car from the CarLot when we need it."

    Can you please give a code example of what the other way would look like.
    I tried to make it myself but I am still confused on the way to set it up.

    • nascardriver

      Hi Maxpro!


    I have a question about your design for the Patient/Doctor classes. Isn't it more appropriate to allow the patients to choose their doctors and no the other way around?

    • Alex

      Not really -- at least in the US, patients can decide what doctors they'd like to see, but ultimately it's up to the doctors to decide if they want to see the patient or not (some doctors are full and not accepting new patients).

      But, for what it's worth, the code would support patient's adding doctors if that made more sense for your use case.

  • Mattermonkey

    First of all, these tutorials are good stuff.

    This is probably the wrong lesson to ask these questions, but the car lot example brought them to mind.

    Is a pure static class just a namespace?
    As in, is

    equivalent to

    Actually, I realised while writing this that namespaces can't have private members, but are there any other differences?

    Secondly, is

    equivalent to

    and is that why it's called an enum class?

    • Alex

      Static classes and namespaces are kind of similar. There's a reasonable discussion of when to use which one in stack overflow.

      An enum class and a class with a public enum aren't the same, though the usage of both would be similar. I haven't read any anecdotes to indicate whether the naming of an "enum class" is related to a having a class with an enum, or whether it was just trying to save on adding new keywords. Maybe both.

  • Janez Novak

    I have seen in this  nad 10.3 chapter that contrarry to what is taught in chapter on overloading operator<<(we just do friend std::ostream& operator<<... foward declaration and then define it outside class) friend std::ostream& operator<< is defind here, in class(by defined I mean adding {....}). Where to use each of these two techniques? Thanks for grad tutorial!

    • Alex

      Generally it's better to define your functions outside the class (in a separate .cpp file). In most of the tutorials here, we do it inside the class to keep the examples concise and make it easier for you to try yourself.

  • Janez Novak

    In first example I can't figure out where "m_patient.push_back(pat)" comes from, specificaly where "push_back" is defined. Can anyone pleas help me with that?

  • Lim Che Ling

    "friend Doctor;"  --> "friend class Doctor;"?

  • Haresh

    1.Could you explain why both the vector types are pointers ( vector< Doctor*>...)?
    2. Furthermore, could you please elaborate how this step works? {pat->addDoctor(this);}

    • Alex

      1) If the vectors didn't contain pointers to Doctors, then the vector would manage the existence of the Doctor. Having the vector hold pointers to Doctors means the vector only owns that pointer, not the Doctor itself.

      2) When addPatient(pat) is called, "this" points to the Doctor the Patient is being added to, and "pat" points to the Patient. Calling pat->addDoctor() should be obvious -- we're calling the Patient::addDoctor() member function on Patient pat. Passing in "this" gives us a way to pass the implicit Doctor object from the Doctor::addPatient() function to the Patient::addDoctor() function.

      • Xarxos

        1) What if you made the vectors hold actual Doctor- and Patient-objects (rather than pointers), then made the addPatient- and addDoctor-functions take references to such objects as parameters? Then the Doctor- and Patient-objects would still exist independently of one another and the vectors would only manage the existence of the references, right? Wouldn't that work just as well as using pointers (in terms of functionality at least, I don't know about performance)?

        • Alex

          > What if you made the vectors hold actual Doctor- and Patient-objects (rather than pointers)

          Then you'd end up with a lot of duplicate copies of Doctors and Patients. For example, Patient's Dave and Betsy would both have an independent copy of Doctor Scott. But that's a bit weird, since the Patient's don't "own" the doctors. If we wanted to update Scott's information (e.g. his age, or specialties), we'd have to ensure all the copies got updated.

          So no, it wouldn't work as well.

          • Xarxos

            Even if we added references to the original doctors/patients to the vectors, rather than copies?

            Like this:

            So here I made m_patient into a vector of Patient-objects rather than pointers, and made the addPatient-function take a Patient-reference (&pat) rather than a pointer to a Patient, and then this reference is added to the vector.

            If I now define p1 and d1 as objects rather than pointers and then pass p1 as an argument into d1's addPatient-function, like so:

            wouldn't this result in a reference to p1 being added to d1.m_patients, and not a copy? So if any changes are made to p1 in the future, those changes will automatically apply to the reference inside d1.m_patients as well? So there wouldn't be any issue with multiple copies of Patients and Doctors, nor any issue with making sure copies are updated properly?

            And of course I mean for the Patient-class to do the same, by having a vector of Doctor-objects and by having its addDoctor-function take a reference to the Doctor as parameter.

            Maybe I'm missing something here, but hopefully you can see where I'm coming from.

            • Alex

              The references just prevent the Doctor or Patient from being copied when you pass it to the add function. Your std::vectors will still hold copies.

              In order to do what you're suggesting, you'd have to create arrays of references. But C++ don't allow you to do this. So we have to use arrays of pointers.

              • Xarxos

                Aaaah, I see, so when I pass the Patient to the addPatient-function, it will be passed as a reference and not a copy, but when I try to add that reference to m_patients, it actually makes a copy of the reference and adds that to the vector? So the vector doesn't actually hold a reference to the original Patient, but a copy that was copied from the reference? That makes sense, thank you!

  • Jacques


    the deleted constructor is made private. Since nothing is supposed to call it, wouldn't it be better to make it public?
    My point is, if you try to call it, the compiler will complain that's illegal because of it being private, rather than your intention of not using it. What are other side effects of making the deleted constructor private/public? (I have read something about static functions accessing it or not at StackOverflow but was a poor explanation. That's why I love this tutorial it explains everything so well it makes me actually want to take my phone and keep learning at any moment as if it was an addictive phone game :)

    • Alex

      Yes, what you're saying makes sense -- because we don't want people creating CarLot() objects, if we make the deleted constructor public, the compiler will give a clearer warning that this is disallowed rather than being masked by the private access control. I've updated the example. Nice thinking!

      Static members (such as getCar()) can still access the static members, but that's what we're desiring in this case.

  • artonix

    Please help.I don't get it this

    pat.m_doctor[count]->getName() and

    Here, are we not already mentioned the name. ( doc.m_patient[count] = "Dave"  ?  Why is that we use ->getname().

    • Alex

      doc.m_patient[count] returns a Doctor object. We need to use the getName() member function to get the Doctor's name from it.

      • artonix

        For example I wrote this code.Can you explain me please ? If I add .getName at the end of the a.array[count] ı get an error.Normally it works well I know yours is different but I don’t get it exactly what I don’t understand.

        #include <vector>
        #include <string>
        #include <iostream>

        class A{
            std::string m_name;
            std::vector<std::string> array;
            std::string getName(){return m_name;}
            void AddName(std::string a){
            friend std::ostream& operator<<(std::ostream &out,A &a){
                  int length = a.array.size();
                for (int count = 0; count < length ; ++count)
                    out << a.array[count] << std::endl;
                return out;
        int main(){
            A object;
            std::cout << object << std::endl;
            return 0;

        • artonix

          Ok man I got it thank you. :)

        • Alex

          Your vector is a vector of std::string, so you can insert and modify strings directly. My vector was a vector of some class containing a std::string. Because the std::string was contained inside the class object, we needed to use a member function to retrieve it.

  • lambdaGirl

    "Consider the simplified case where a Course can only have prerequisite."

    should be replaced with:

    "Consider the simplified case where a Course can only have one prerequisite."

  • Help

    Hi Alex,
    I wounder in exempel patient and doctor. let's asume the program doesnt end when u delete patient and doctor. then the std::vector(*patient/doctor) would be left with dangling point so u need to set them to nullptr, right?

    Is there a way to pop_back a specific of them? lets asume i wanna just take away p2?

    • Alex

      You're correct, if we deleted any of the Doctors or Patients without removing them from the vectors, the vectors would be holding dangling pointers. That's not a problem here, because we don't do the deletes until the program is ending anyway. But it's a good question in general.

      While moving the last element of a vector is easy (use pop_back), removing an arbitrary element from a vector is not straightforward. But you can do so like this:

      • Help

        Hi Alex!
        Thanks for ur great respond!:)
        I had problem with the one u used and found another that worked
        vec.erase(vec.begin() + i);  // where i is the possition of the vector!

        I got one more follow up question. let's say Dave (patient) change doctor so Scott is no longer having him ( let's just focus on so we just wanna delete from doctor m_patient). So i wanna make a function that give Doctor option to write a name and it will delete it from vector ( just wanna know how it works) lets say i got a std::cin>> deletePatient; and he types "Dave" how do i search in the vector for "Dave" possition which is m_patient[0] so i can put it in vec.erase and delete him from vector?

        Many thanks really good learning page:)

        • Help

          well i meant the order should be like this:
          1. First delete Dave from heap memory.
          2. point Dave to nullptr.
          3. then take it out from the std::vector through vec.erase

          • Alex

            No. First remove Dave from the vector, then delete it. If you delete Dave before removing it from the vector, the vector will be pointing at deallocated memory, which means when you go to see if the element is Dave, then you'll be accessing deallocated memory, which will cause undefined behavior.

            • Help

              Thanks for making it clear. I wounder if i did vector.push_back(new Patient("Dave")). and if i did erase from vector first can i still access it so i can delete it?

              like in last quiz in 12.x after we add all those circle and triangle and then we deleted them. should we not pop_back so the vector is empty?

              • Alex

                Yes, if you push_back a Patient, you can pop_back that same Patient off the stack and then delete it.

                If we intended to continue using the vector, we'd definitely want to get rid of all the stuff we'd deleted manually. However, since the program is ending anyway, it doesn't matter. The vector will clean up after itself (note: it will not delete the pointers, which is good, since we've already done that manually).

        • Alex

          This is also more difficult than it seems like it should be. See these answers for some various ways to do this.

  • Omri

    "We'll implement this function below Doctor since we need Doctor to be defined at that point."
    "We'll *define* this function below Doctor *definition* since we need Doctor to be defined *already for this function to be successfully defined*."

  • Omri

    "They should use Doctor::addPatient() instead, which is publicly exposed"
    Accurate but does not explicitely expose to the "unseasoned"  the association scheme to be used.
    Consider something of the sort:
    "We plan the association patient-doctor to occur at the same place where the association doctor-patient occurs. Thus when Doctor::addPatient(...) will be launched, it will launch Patient::addDoctor(...) appropriately so that the two associations will be properly implemented. For this scheme to work Patient::addDoctor(...) needs to be visible to Doctor objects (only, thus not being public) as will be arranged for below through appropriate befriending."
    Is this correct?


    Alex Bro I love you. I learned a lot from this site. thank you senpai

  • Moj

    Alex would you please explain how we might use that Course/prerequisite example? I'm interested in its design... It has no string members for example!

    • Alex

      It probably makes more sense for each course to have a name (or numeric id) so we can differentiate them. I've updated the example to include a name member for each course.

  • Johnbosco

    please I need clarification on how the following lines of codes executes

    please I can't move on without understanding the above. Thanks in advance

    • Alex

      The data looks like this:

      d1->m_patient = ["Dave"]
      d2->m_patient = ["Dave", "Besty"]
      p1->m_doctor = ["James", "Scott"]
      p2->m_doctor = []
      p3->m_doctor = ["Scott"]

      The code you pasted is from a Doctor member function, where doc is set to whichever doc we called (*d1 or *d2). It looks through all of the patients for that Doctor and prints their names.

      The Patient code works similarly, but iterates through each Patient's Doctors.

  • Christopher

    In the examples above (and in the previous chapter), why do we need to use pointers for some of the objects?

  • Anonymous

    Hi, I have a question. On line 82 how come you do not include the Class Name Patient to the overloaded operator << like the way you did the

    Is it not required in this context - if not how come?

  • Daniel

    Grammar fix (plural doctors doesn't quite make sense):

    Change "The relationship between a doctors and patients is a great example of an association."

    To: "The relationship between a doctor and its patients is a great example of an association."

  • Hugh Mungus

    Is there a reason you used a normal for loop rather than a for each loop?

  • Alex

    Typo fixed, and good idea. Lesson updated.

  • Tyler

    Under the "Reflexive Association" header, in the first sentence you accidentally called it "reflective" association. Great tutorials, thanks!

    Also, in the indirect association example (cars in a lot) it may be more clear to say:


    ...rather than hard-coding the "17" again, to demonstrate how the Driver object is associated with the Car object.

  • Surya

    Hi Alex, I'm so lucky to find ur tutorials.
    I wanted to know if there is any difference between aggregation and association other than the direction.

    • Alex

      In terms of how aggregation and associations are implemented, there's usually little difference in C++. The differences between the two are mostly conceptual.

  • Gajendra Gulgulia

    Hi Alex,

    Can you please explain the syntax in line 28 of the Doctor-Paitent code :
          std::string getName() const

    i cannot seem to understand the role of 'const' at the end of function getName()

    thanks in advance

    • Alex

      Const in this context means getName() is a const member function -- that is, getName() promises not to modify any of the member variables, or call any non-const functions.

      Const objects can only call const member functions (and constructors/destructors).

  • Bing

    Hello Alex

    First of all, thank you for your generosity on sharing your tremendous knowledge of c++. There is one thing, regarding the DOCTOR and PATIENT program, I am not sure about. I copied and pasted part that I don't know

    void addPatient(Patient *pat)
            // Our doctor will add this patient
            // and the patient will also add this doctor

    how does that pointer-to-member operator, -> , work here? because later you write like following in your program



    Can you explain the mechanism behind there with this pointer-to-member operator? Thank you!

  • Matt

    Typo at the beginning: "quality" should be "qualify" I think.

  • Matt

    Typo at the begininng, after the "Association" sub-heading, first sentence... I think "and" should be "an".

    Also, your overloaded operator<< functions for both Doctor and Patient use "std:cout" instead of "out".

  • Ahmed

    Great explanation , thank you :))

Leave a Comment

Put all code inside code tags: [code]your code here[/code]