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 ?
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.