Search code examples
c++objectoopmany-to-many

Change the Code Using Pointers to Achieve Many-to-Many Relationship


I have the following code in Movie.hpp

#ifndef MOVIE_H
#define  MOVIE_H

class Movie
{
  private:
    std::string title;
  public:
    std::string getTitle() const {return this->title;}  // const added 
    void setTitle(std::string newTitle){this->title = newTitle;}
};
#endif

In Actor.hpp

#ifndef ACTOR_H
#define  ACTOR_H

#include "Person.hpp"
#include "Movie.hpp"

class Actor: public Person
{
  private:
    std::vector<Movie> movieList;
  public:
    void addMovie(Movie newMovie){this->movieList.push_back(newMovie);}
    void printMovies()
    {
      for(Movie movie: this->movieList)
      {
        std::cout << movie.getTitle() << '\n';
      }
    }
};

#endif

In the Main executable file Main.cpp

 Movie movie1, movie2, movie3;        
  Comment comment1, comment2, comment3;
  Viewer viewer1, viewer2;
  Actor actor1, actor2;
  Celebrity celebrity1;

  movie1.setTitle("Transformers");
  movie2.setTitle("Interstellar");
  movie3.setTitle("The Matrix");

  actor1.setName("Brad");
  actor1.setAge(57);
  actor1.addMovie(movie1); //here
  actor1.addMovie(movie2);

  actor2.setName("Justin");
  actor2.setAge(30);
  actor2.addMovie(movie1);   // added
  actor2.addMovie(movie3);

Now, I have been instructed that my code is not following the many-to-many relationship. In other words, if actor1 and actor2 have a connection with the same movie, they should share the same 'Movie' object. In my case, they have different movie objects. The proper way is to store pointers to Movie inside Actor:

std::vector<Movie*> movieList;

(collection of pointers, not of objects)

and inside Movie:

std::vector<Actor*> actorList;

Another proper way, I prefer this one, is to add a separate class

class Role {
private:
   Actor* actor;
   Movie* movie;
};

and store collection of such objects inside Movie and Actor, here values (not pointers) are OK

std::vector<Role> movieList; //inside Actor

std::vector<Role> actorList; //inside Movie

So, the actual problem is I'm unable to implement above solution. Can someone please tell me the changes that will be made in previous code if I add pointer or Role object as instructed above ?


Solution

  • If the Movie object needs to be shared between Actors, another way to do this is to use std::vector<std::shared_ptr<Movie>> instead of std::vector<Movie> or std::vector<Movie*>.

    The reason why std::vector<Movie> would be difficult is basically what you've discovered. The Movie object is separate from another Movie object, even if the Movie has the same name.

    Then the reason why std::vector<Movie*> would be a problem is that yes, you can now "share" Movie objects, but the maintenance of keeping track of the number of shared Movie objects becomes cumbersome.


    In comes std::vector<std::shared<Movie>> to help out. The std::shared_ptr is not just a pointer, but a smart pointer, meaning that it will be a reference-counted pointer that will destroy itself when all references to the object go out of scope. Thus no memory leaks, unlike if you used a raw Movie* and mismanaged it in some way.

    #ifndef ACTOR_H
    #define  ACTOR_H
    
    #include "Person.hpp"
    #include "Movie.hpp"
    #include <memory>
    
    class Actor: public Person
    {
      private:
        std::vector<std::shared_ptr<Movie>> movieList;
    
      public:
        void addMovie(std::shared_ptr<Movie> newMovie) {this->movieList.push_back(newMovie);}
    
        void printMovies()
        {
          for(auto& movie: this->movieList)
          {
            std::cout << movie->getTitle() << '\n';
          }
        }
    };
    
    #endif
    

    Then putting together a minimized version of your classes, and a test main function:

    #include <memory>
    #include <vector>
    #include <string>
    #include <iostream>
    
    class Movie
    {
      private:
        std::string title;
        std::string m_rating = "G";
        
      public:
        std::string getTitle() const {return this->title;}  // const added 
        void setTitle(std::string newTitle){this->title = newTitle;}
        void setRating(std::string rating) { m_rating = rating; }
        std::string getRating() const { return m_rating; }
    };
    
    class Person {};
    
    class Actor: public Person
    {
      private:
        std::vector<std::shared_ptr<Movie>> movieList;
    
      public:
        void addMovie(std::shared_ptr<Movie> newMovie) {this->movieList.push_back(newMovie);}
    
        void printMovies()
        {
          for(auto& movie: movieList)
          {
            std::cout << movie->getTitle() << "  Rating: " << movie->getRating() << '\n';
          }
        }
    };
    
    int main()
    {
      // Create our movies 
      auto movie1 = std::make_shared<Movie>();
      auto movie2 = std::make_shared<Movie>();
      auto movie3 = std::make_shared<Movie>();        
    
      Actor actor1, actor2;
      
      movie1->setTitle("Transformers");
      movie2->setTitle("Interstellar");
      movie3->setTitle("The Matrix");
    
      actor1.addMovie(movie1); 
      actor1.addMovie(movie2);
    
      actor2.addMovie(movie1); 
      actor2.addMovie(movie3);
      
      std::cout << "actor 1 movies:\n";
      actor1.printMovies();
      std::cout << "\nactor 2 movies:\n";
      actor2.printMovies();
      
      // Set the rating to movie1 to "R"
      movie1->setRating("R");
    
      std::cout << "\n\nactor 1 movies:\n";
      actor1.printMovies();
      std::cout << "\nactor 2 movies:\n";
      actor2.printMovies();
      
    }
    

    Output:

    actor 1 movies:
    Transformers  Rating: G
    Interstellar  Rating: G
    
    actor 2 movies:
    Transformers  Rating: G
    The Matrix  Rating: G
    
    
    actor 1 movies:
    Transformers  Rating: R
    Interstellar  Rating: G
    
    actor 2 movies:
    Transformers  Rating: R
    The Matrix  Rating: G
    

    So now actor1 and actor2 now actually share the same Movie called "Transformers" -- they are no longer separate objects. I added a setRating and getRating functions to demonstrate that changing the rating of movie1 changed the rating to R for both actor1 and actor2.


    Now, nothing stops you from creating another movie with the same name (for example, a movie4 with the name "Transformers"). If that is an issue, you will need an additional data structure that keeps track of the names that have already been used (probably a std::unordered_set<std::string>). But that is a side point, and only stated just to let you know that this could happen.