0

I know the question is going to sound weird, so let me start of with the issues:

Problem 1

I'm making a Monopoly game (for which a Monopoly class has been made that has a PlayerList member, containing players), and I had implemented an Auction class. I recently discovered that I have a lot of comparable code in the Auction class and the PlayerList class, so I put a lot of Auction-code in the BidderList class.

// Auction.cpp
Auction::Auction(Property* property, PlayerList* bidderList, int currBidderIndex)
    : m_property{ property }, m_bidderList{ new BidderList(bidderList, currBidderIndex) },
    m_highestBid{ startBid }, m_previous_has_left{ false }
{
}

// BidderList.cpp
BidderList::BidderList(PlayerList* playerList, int currBidderIndex)
    : PlayerList(playerList)
{
    m_currPlayerIndex = currBidderIndex;
}

However... in my implementation of an Auction, it's used as a temporary object that will be destroyed after it's done. (Yes, I could give it setters.) Since I'm using the original player objects (which is needed to modify them), I'm also deleting those in the base destructor (unintentionally) when the BidderList object gets destroyed:

PlayerList::~PlayerList()
{
    for (const auto player : m_players)
        delete player;
}

... which forced me to do something like this:

// Prevent base class from deleting the players! - not so clean
BidderList::~BidderList()
{
    int size = getSize();
    for (int i = 0; i < size; ++i)
        m_players[i] = nullptr;
}

I still want to use my players after the Auction, since it's not sure the game will have ended. I was wondering if I could do this on a cleaner way.

Problem 2

I was also thinking about making deriving Bidder and (Trader for Trade) from the Player class (which doesn't solve the previous problem in any obvious way), because this way I could again achieve higher cohesion. Although the problem isn't big in this situation, I still would like to know an answer to the following problem:

Somehow, each player acts as a bidder in during an Auction (which is still temporary).

  • I can copy each player into a bidder, but then I won't be modifying the actual player (which I want, of course).
  • I can make the Bidder in such a way that it contains a pointer to the actual object, that would mean I have to relink every method of the Player to the bidder, where I should actually be using inheritance.

If I somehow manage to convert the actual player to a bidder (so I would be using the original player object), then I still have to make sure only the derived class is destroyed - sounds like since fiction to me. Is there no other way around then using the composite relation here? Because, it would look like this and it ain't pretty:

bool Derived::foo()
{
    return m_base.foo();
}

In both problems, I have a need for a temporary derived class, constructed with an existing parent, which needs to be cleaned up afterwards in a safe way. Could you please help me with making the right decision here? (Yes, I do want to keep the Auction object a temporary object, just for practice). Thanks in advance!

  • A list of referenced to chickens and a list of chickens are related in that read-only access can be done on both. But I suspect the glue code is going to be bigger than the duplicate codebifbyou implement them seperately. Your bit about destroying derived classes is nonsense; C++ does not work that way. – Yakk - Adam Nevraumont Jun 16 '20 at 23:22
  • 1
    You can make `m_players` a `std::vector>` if you want a `PlayerList` to *share* ownership of `Players` between different `PlayerList`s (such as your master player list and your bidder list). – JohnFilleau Jun 16 '20 at 23:27
  • @Yakk, I know that is not possible, that's why I'm asking if such has to be done using composition –  Jun 16 '20 at 23:27
  • @John, that's actually not a bad idea at all! Thanks, I hadn't thought about that. –  Jun 16 '20 at 23:30
  • 3
    Another option is to scrap `BidderList` entirely and just have a `std::vector> bidders`. https://stackoverflow.com/questions/922360/why-cant-i-make-a-vector-of-references – JohnFilleau Jun 16 '20 at 23:30
  • That's probably safe. Even if a bankrupted player is removed from the main list (invalidating references to players in the list), any unfortunate player is unlikely to be removed during the auction. – user4581301 Jun 16 '20 at 23:39
  • Hey, I've thought about it the abstract problem (one child becoming another temporary child) and gave an example of how I would handle such problem now. I would like to know what you guys think of it. See the answer to problem 2 below. Thanks in advance! –  Jun 18 '20 at 15:42

1 Answers1

0

I have thought about using std::shared_ptr<Object> for a while, and I think I have found the solution to the second problem, although it still has the little downside that every member that needs to be modified in child classes, has to be a shared_ptr, even when you wouldn't make it a pointer. See the code below, representing classes for employees going on holidays.

Person

/* - - - - - - - HEADER- - - - - - - -*/
#pragma once
#include <string>
#include <memory>

class Person
{
public:
    Person(std::string name, int capital = 1000);
    std::string getName() const;
    virtual void print() const = 0;
protected:
    std::string m_name;
    std::shared_ptr<int> m_capital;
};

/* - - - - - - - SOURCE- - - - - - - -*/
#include "Person.h"

#include <iostream>

Person::Person(std::string name, int capital)
    : m_name{ name }, m_capital{ std::shared_ptr<int>(new int{capital}) }
{
}

std::string Person::getName() const
{
    return m_name;
}

Employee

/* - - - - - - - HEADER- - - - - - - -*/
#pragma once
#include "Person.h"

class Employee : public Person
{
public:
    Employee(std::string name, std::string workName);
    std::string getWorkName() const;
    void print() const override;
private:
    std::string m_workName;
};

/* - - - - - - - SOURCE- - - - - - - -*/
#include "Employee.h"

#include <iostream>

Employee::Employee(std::string name, std::string workName)
    : Person(name), m_workName{ workName }
{
}

std::string Employee::getWorkName() const
{
    return m_workName;
}

void Employee::print() const
{
    std::cout
        << m_name << '\n'
        << "  I'm an employee at " << m_workName << '\n'
        << "  I have a capital of " << std::to_string(*m_capital) << '\n'
        ;
}

Tourist

/* - - - - - - - HEADER- - - - - - - -*/
#pragma once
#include "Person.h"
#include <queue>

class Tourist : public Person
{
public:
    Tourist(std::string name, std::queue<std::string> destinations);
    Tourist(const Person& person, std::queue<std::string> destinations);
    void visitNextDestination();
    void print() const override;
private:
    std::queue<std::string> m_destinations;
};

/* - - - - - - - SOURCE- - - - - - - -*/
#include "Tourist.h"

#include <iostream>

Tourist::Tourist(std::string name, std::queue<std::string> destinations)
    : Person(name), m_destinations{destinations}
{
}

Tourist::Tourist(const Person& person, std::queue<std::string> destinations)
    : Person(person), m_destinations{destinations}
{
}

void Tourist::visitNextDestination()
{
    if (m_destinations.size() > 0) {
        m_destinations.pop();
        *m_capital -= 100;
    }
}

void Tourist::print() const
{
    std::cout
        << m_name << '\n'
        << "  I'm a tourist heading for " << m_destinations.front() << '\n'
        << "  I have a capital of " << std::to_string(*m_capital) << '\n'
        ;
}

Main.cpp

#include "Employee.h"
#include "Tourist.h"

int main()
{
    std::queue<std::string> destinations;
    destinations.push("Paris");
    destinations.push("London");
    destinations.push("Berlin");

    std::vector<Person*> persons{
        new Employee{"Harry", "Microsoft"},
        new Employee{"Frank", "Apple"},
        new Tourist{"Bob", destinations}
    };

    for (const auto person : persons)
        person->print();

    Tourist* frank = new Tourist{ *persons[1], destinations };
    frank->visitNextDestination();
    frank->print();

    delete frank;
    persons[1]->print();
}

Output

Harry
  I'm an employee at Microsoft
  I have a capital of 1000
Frank
  I'm an employee at Apple
  I have a capital of 1000
Bob
  I'm a tourist heading for Paris
  I have a capital of 1000
Frank
  I'm a tourist heading for London
  I have a capital of 900
Frank
  I'm an employee at Apple
  I have a capital of 900
  • Ehmm. woops, I forgot to clean whatever is written in main.cpp. Seems like I should have used the `unique_ptr`s once again. –  Jun 18 '20 at 15:44