how to create array of an abstract class in c++ - c++

I am trying to create a program that takes food order and prints it out. I have my base class Food which has a pure virtual function in it. Class Food has 2 subclass Pizza and Dessert. I am trying to make an array of Food in my main so when a customer orders Pizza or Dessert, it will be stored in the array of Food. But every time I try, I get an error. How should I put the two items together then if I want to use a loop to go over each item the customer ordered?
This is my code:
int main()
{
Dessert d("brownie");
Pizza p("BBQ delux");
Food array[2] = {d,p};
}
This is my error message. (NOTE: get_set_price() and print_food() are my pure virtual functions which is defined in base class and implemented in the 2 subclasses)
main.cpp:37:14: error: invalid abstract type ‘Food’ for ‘array’
Food array[2] = {d,p};
In file included from main.cpp:4:0:
Food.h:5:7: note: because the following virtual functions are pure within ‘Food’:
class Food
^
Food.h:20:15: note: virtual void Food::get_set_price()
virtual void get_set_price()=0;
^
Food.h:27:15: note: virtual void Food::print_food()
virtual void print_food()=0;
^
main.cpp:37:22: error: cannot allocate an object of abstract type ‘Food’
Food array[2] = {f,o};
^

You cannot create instances of abstract classes, but you can assign the pointers or references of concrete derived instances to pointers or references of the base class respectively.
int main()
{
Dessert d("brownie");
Pizza p("BBQ delux");
Food* array[2] = {&d,&p};
}
then work with array
array[0]->print_food();

You need reference semantics for that, because Food arr[2]; tries to initialize the array with default values (which are abstract, thus not constructible).
I think std::array<std::unique_ptr<Food>, 2> arr; should be the most natural to use in this case.
std::array<std::unique_ptr<Food>> arr = {
std::make_unique<Dessert>("brownie"),
std::make_unique<Pizza>("BBQ delux")
};
If you just want to loop over those two values, though, using initializer_list would be easiest, I suppose.
for (auto f : std::initializer_list<Food*>{&d,&p})
f->commonMemberFunction();
Unfortunately it won't deduce the correct type from just {}, but a helper could be created, I suppose,

Starting with C++11, you can use std::reference_wrapper too. It's very similar to #Mykola's answer, but uses references:
#include <functional> // for std::reference_wrapper
int main()
{
Dessert d("brownie");
Pizza p("BBQ delux");
std::reference_wrapper<Food> array = {d, p};
// just to see they're references, they have same memory addresses as
// the initial variables.
for (const Food &f : array) {
std::cout << &f << " ";
}
std::cout << "\n" << &d << " " << &p << "\n";
}
Unfortunately the same restriction applies to this and to the version with pointers. You need to have local variables defined, you can't just use anonymous objects here.
This won't work:
std::reference_wrapper<Food> array = {Dessert("brownie"), Pizza("BBQ delux")};

Related

Inheritance and Function Arguments

My book covered superficially many test topics from the Object-Oriented domain and I need some explanation and a hint which book does cover such topics.
There's a test question:
#include <iostream>
class A {
public:
virtual void f(int n=2) {// takes the argument value from here
std::cout << n+1 << " in A";
}
};
class B : public A {
private:
virtual void f(int n=3) {
std::cout << n - 1 << " in B";// this class is used
}
};
int main() {
A* p = new B;
p->f();
}
Output:
1 in B, that is the argument from A fed into B.
I understand why the object of the class B is created. I don't understand why is the value of 2 taken as the argument. I also checked if the class A's value would be taken if the variable's name were changed -- it is still used.
Any literature to read about this or educational videos on such OOP topics is welcome.
You have more info here : Can virtual functions have default parameters?
The idea is "the static type of p is A so it will use the default value of A::f"
IMHO, it's a detail and probably not what you should be focusing if you are learning OOP, but it's great you did see it.

Storing list of names associated with tasks

So i have made this task system, or i'm trying to make it, so that if someone has done more than seven tasks, the next person has to do seven.
the doubles (Bodine, Finn, Tycho) are the persons. Now i dont know if i used it correctly,(if they need to be doubles) and how i use them in this line of code:
if (taskNumbers == 7)
{
std::cout << "It's " + /*what to do here? */ + "time!";
what i want is that if the task numbers are higher than seven, and Bodine has done 7 tasks, it says "Its finn his time!"
#include <iostream>
using namespace std;
double Bodine;
double Finn;
double Tycho;
bool tasksDone = true;
int taskNumbers = 0;
int main()
{
if (taskNumbers > 7)
{
std::cout << "It's " + /*what to do here? */ + "time!";
return 1;
}
}
This is one of my first projects yet(im 13, so yeah....). I only need to know how to use the next variable!
Well, you're not really keeping track of whose turn it is. Also, double is not the right tool for the job here. I would do something like this:
std::vector<std::string> users = { "Bodine", "Finn", "Tycho" };
int currentUser = 0;
Now we have a list of names as well as a counter that says whose turn it is. 0 is for the first person, 1 for the second person, and 2 for the third person. Then we need the logic to advance that counter:
void advanceUser() {
if (++currentUser >= users.size()) {
currentUser = 0;
}
}
This increases currentUser, and when it is bigger than the amount of people, it loops back to 0, so the people take turns correctly.
Next about the task logic, I would suggest something like this:
int tasksDone = 0;
void incrementTasksDone() {
if (++tasksDone >= 7) {
advanceUser();
std::cout << "It's " + users[currentUser] +"'s time!" << std::endl;
tasksDone = 0;
}
}
Here we have a counter that keeps track of the amount of tasks, and when it hits 7, it sets it back to 0 and announces that it's the next user's turn after callling advanceUser to set that right.
You can then call incrementTasksDone however you want, for instance I tested it like this:
int main()
{
for (int i = 0; i < 100; i++) {
incrementTasksDone();
}
}
That would get 100 tasks done, for instance, so the turns would change 14 times in the process and it would print the message every time.
Also, to get this example to run, be sure to add the following includes:
#include <vector>
#include <string>
#include <iostream>
Hello and welcome to C++. Many will say that C++ is a bad language of choice for starters. Well I started with C++ myself a long time ago and I'm still learning to this day. I am 100% self taught and I didn't have the advantages that people do today with the amount of information that is available over the internet as well as the modern formats that are available. When I first started learning C++, the internet was in the stage of booming; but most websites back then were nearly pure text and even simple pictures or graphics took a while to load to the screen because this was in the era of Dial Up. Today, people who are starting out have the advantages of both this website, other similar sites and even youtube videos. However, I still enjoy helping where I can because it doesn't only help you, but it also helps me to improve what I have already learned. C++ has evolved over the years so what I'll do here is demonstrate to you a small application that I believe mimics the behavior of what you have described you are trying to do. Some of these techniques are a little advanced especially for beginners, however I think it is a good fit that someone who is new learns these concepts early.
Storage Types & Lifetime - There are basically 4 main storage types in C++: Automatic, Dynamic, Static and Thread. I mostly focus on the first 3.
Automatic: It has the lifetime of the scope that it is declared in and will automatically be destroyed once that scope exits from its closing brace }
Dynamic: Memory that is represented by pointers but declared with new and must have a matching delete, or arrays new[] and delete[] respectively. They can live longer than the scope they are declared in. They will live until their matching delete is called. If no matching delete takes place, this leads to a memory leak, invalid pointers, dangling pointers & references and undefined behavior. Special Care needs to be taken when using raw-pointers; it's advisable to use either containers or smart pointers.
Static: These are typically found in the global namespace and or global filespace. If a static is declared in the main.cpp it will have a lifetime of the application and the scope of the whole program. If they are declared in other cpp files, they will have the scope of that file, unless if they are declared in some header file, then they will have the scope of what other translation unit includes that header. They are similar to Automatic in the sense they will automatically be destroyed, but they differ as in the fact that they are initialized only once, the maintain their state and you can only have a single instance of them.
For a demonstration of the different types of storage classifications you can see my previous answer to this Q/A.
Classes and Inheritance: - (I will not involve Polymorphism).
Classes:
Classes and Structs are user defined data types.
The difference between the two is the default access
By default: Structs have Public Members & Classes have Private Members
Members of a Class or Struct can be any built in type, pointers to types, another user defined data type, and methods or functions.
Member Variables can be of any Storage Type: Automatic, Dynamic, Static and Thread, however, member functions are usually Automatic but can be Static. If you have a member variable it has to be initialized outside of the Class's Declaration to resolve it's symbols.
They have Constructors and Destructors by default, but one can create their own custom Constructor or Destructor. You will typically see people mention them by their short names: ctor & dtor respectively.
One class can inherit from another.
Inheritance:
You have Base or Super Classes, and you have Derived or Child Classes
When Inheriting from a Base class, if the Base class's ctor is Public, this means you can create an object of both the Base & Derived Classes. Sometimes you want this kind of design, but there are times when you don't.
If you do not want the user to be able to create an instance of the Base Class, but are able to create an instance of the Derived Class then all you need to do is make sure that 1st its ctor is Protected. If you declare it Private, then even your Derived Classes can not access it and therefore they can not be declared. *2nd You want to make sure that your Destructors - dtors are Virtual; otherwise you will have issues with the order in which classes are destroyed.
A Base Class can have Member Functions that are Virtual which means All Derived Classes must Implement that Function.
A Base Class can have Member Functions that are Purely Virtual which is similar above, but also prevents anyone from declaring an instance of this Base Class because this makes the Base Class Abstract which means it's an idea for an interface.
Example Virtual Function: virtual update();
Example Purely Virtual : `virtual update() = 0;
Containers - (I will use a std::container, but I won't involve any Algorithms)
There are many kinds of containers for different needs; these containers help to keep your program simple, easy to manage and debug, user friendly and to prevent many future headaches instead of using basic C Arrays.
There are a few different types and they each have their own purpose and properties which I will not go over all of their details as you can find a plethora of information on the net regarding them, but I will label and group them to their similar properties: The groupings are Sequence Containers, Associative Containers, & Unordered Containers and these all belong to the std:: namespace. The major difference between the groupings are: Sequence Containers are like arrays and linked lists, Associative Containers are binary trees, and Unordered Containers are similar to the binary trees except they are not in order, they are considered hash tables.
Sequence: vector, dequeue, queue, list, forward_list, array
Associative: set, multiset, map, multimap
Unordered: unordered_set, unordered_multiset, unordered_map, unordered_multimap`
What makes these containers powerful is the ability to either traverse them quickly or to insert and find quickly depending on which container you are using. Another good feature is helping in the process of memory management. Finally is the numerous algorithms and iterators that can do work on them.
For more information on the stl I advise to watch this youtube series by Bo Qian
Smart Pointers There are a few different types, but the most important two are std::shared_ptr<T> and std::unique_ptr<T>. The major difference between the two is that shared has a reference count to how many objects have access to it; it means it has a public type interface and can use copy semantics; unique on the other hand has only 1 owner at a time, it can transfer ownership but once it does the original owner no longer has access to it. You can not copy unique, but you can move them. These smart pointers help with the use of dynamic memory and life time object management to prevent memory leaks, invalid pointers, dangling pointers and references, undefined behavior etc. They help to minimize the use of new & delete and new[] & delete[] respectively.
Now that you have an understanding of some of the concepts that you will see quite often in C++ programs; I will now show you the simple application that I have written based off of what it was that I perceived you were trying to do:
#include <array>
#include <exception>
#include <memory>
#include <string>
#include <iostream>
class Person {
protected:
std::string name_;
int tasksCompleted_;
public:
const std::string& whoIs() const { return name_; }
int numberTasksCompleted() const { return tasksCompleted_; }
virtual void performTask() = 0;
protected:
explicit Person(const std::string& name) : name_{ std::move(name) } {}
virtual ~Person() = default;
};
class Bodine final : public Person {
private:
static int currentTask;
public:
explicit Bodine(const std::string& name = std::string("Bodine")) : Person(name) {}
virtual ~Bodine() = default;
virtual void performTask() { currentTask++; tasksCompleted_ = currentTask; }
};
int Bodine::currentTask = 0;
class Finn final : public Person {
private:
static int currentTask;
public:
explicit Finn(const std::string& name = std::string("Finn")) : Person(name) {}
virtual ~Finn() = default;
virtual void performTask() { currentTask++; tasksCompleted_ = currentTask; }
};
int Finn::currentTask = 0;
class Tycho final : public Person {
private:
static int currentTask;
public:
explicit Tycho(const std::string& name = std::string("Tycho")) : Person(name) {}
virtual ~Tycho() = default;
virtual void performTask() { currentTask++; tasksCompleted_ = currentTask; }
};
int Tycho::currentTask = 0;
int main() {
try {
std::array<std::shared_ptr<Person>, 3> people{
std::shared_ptr<Person>(new Bodine()),
std::shared_ptr<Person>(new Finn()),
std::shared_ptr<Person>(new Tycho())
};
// For each person in array
const int MAX_TASKS = 7;
int currentPerson = 0;
for (auto& p : people) {
std::cout << p->whoIs() << " has performed task #:\n";
while (p->numberTasksCompleted() < 7) {
p->performTask();
std::cout << p->numberTasksCompleted() << '\n';
if (p->numberTasksCompleted() == MAX_TASKS) {
currentPerson++;
if (currentPerson <= (people.size() - 1) ) {
std::cout << "It's your turn " << people[currentPerson]->whoIs() << " to do some tasks.\n";
}
break;
}
}
}
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
-Output-
Bodine has performed task #:
1
2
3
4
5
6
7
Finn has performed task #:
1
2
3
4
5
6
7
Tycho has performed task #:
1
2
3
4
5
6
7
Link to above program.
I know this is quite a bit to read; but please look closely over the application to try and see what it is doing. You can copy and paste this right into your own IDE and try to build and run it to see it in action. Hopefully this will give you a grasp of what C++ is.
-Edit-
About the code above: it is a little more complex than what it truly needs to be. First off you would not really see a single person's identity as it's own class. I only did this to demonstrate what inheritance is. I choose to use std::array instead of std::vector mainly due to the fact that you had 3 specific people from your proposal. If you have an unknown amount of people at compile time then std::vector would be the correct container to use. You wouldn't really need to use inheritance either. Also if you look at the 3 derived classes there is a lot of duplicate code. I also used shared_ptrs to show how they can be used, but are not necessary in your specific situation. I also used static member variables in the Derived Classes to show static storage.
Here is a simplified version of the above code to mimic your behavior. Both programs do the same task similarly, the only major differences are: The use of inheritance with purely virtual methods, static member storage, container type, and dynamic memory via the use of shared_ptr.
#include <exception>
#include <iostream>
#include <string>
#include <vector>
class Person {
private:
std::string name_;
int tasksCompleted_;
public:
explicit Person(const std::string& name) : name_(name), tasksCompleted_(0) {}
const std::string& whoIs() const { return name_; }
int numberTasksCompleted() const { return tasksCompleted_; }
void performTask() { tasksCompleted_++; }
};
int main() {
try {
std::vector<Person> people{
Person( "Bodine" ),
Person( "Finn" ),
Person( "Tycho" )
};
// For each person in array
const int MAX_TASKS = 7; // Don't like magic numbers!
int currentPerson = 0; // Needed variable
for (auto& p : people) {
std::cout << p.whoIs() << " has performed task #:\n";
while (p.numberTasksCompleted() < MAX_TASKS) {
p.performTask();
std::cout << p.numberTasksCompleted() << '\n';
if (p.numberTasksCompleted() == MAX_TASKS) {
currentPerson++;
if (currentPerson <= (people.size() - 1) ) {
std::cout << "It's your turn " << people[currentPerson].whoIs() << " to do some tasks.\n";
}
break;
}
}
}
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Here is the link for this version of the program!

Derived class utilizing a parent object

I'd like to have a derived class update when an object of type base is updated because they share the same reference to the base. This would be done after the base class is already created.
class Tumor : sc2::Unit {
public:
Tumor(const sc2::Unit *unit) : pointer(unit){}
~Tumor()
float spread = 10.0f;
float vision = 11.0f;
// Other things...
bool operator==(const Tumor& rhs) { return pointer->tag == rhs.tag; }
const sc2::Unit *pointer = nullptr;
};
Rather than accessing
tumor.pointer->tag
I'd like it to be:
tumor.tag
sc2::Unit has a tag variable and when I put
std::cout << "tumor_tag Address: " << &(this->pointer->tag) << "\tunit_tag Address: "
<< &(unit->tag) << std::endl;
in the constructor I would have the same memory location being output.
I'm aware I could store a pointer to sc2::Unit in Tumor (as is shown) but I was trying to find a more elegant solution and my Google-foo is weak or it's not possible.
You can't use the inheritance to share the same instance of the base between multiple instances of derived classes because by the definition derived class creates its own instance of the base.
If you don't want use the tumor.pointer->tag construction, you can write a getter function:
tag_type& Tumor::GetTag() {
return pointer->tag;
}
So you'd use it tumor.GetTag().

using dynamic_cast for runtime type identification

when reading Essential c++ chapter 5.10 Run-time Type identification, I've encountered a problem. Let me introduce a little background first. There are a base class named num_sequence and a class Fibonacci derive from num_sequence. In the base class, there is a virtual function named gen_elems and the derived class has its own definition.
The following comes from the book.
Fibonacci fib;
num_sequence *ps = &fib;
ps->gen_elems(64);
We know that the Fibonacci instance of gen_elems() will be invoked.
However, although we know from this test that ps addresses a Fibonacci
class object, an attempt to invoke the Fibonacci instance of
gen_elems() directly through ps results in a compile-time error:
ps->Fibonacci::gen_elems(64); //gives compile-time error
ps does not know the type of the object it addresses, even if we and the typeid and virtual function mechanisms do.
To invoke the Fibonacci instance of gen_elems(), we must instruct the compiler to convert ps into a pointer of type Fibonacci. The static_cast and dynamic_cast can both do this job.
I'm confused by the bold sentence. ps->gen_elems(64) actually invokes the Fibonacci instance of gen_elems(). Why need we use static_cast and dynamic_cast to convert it to a pointer of type Fibonacci?
Under most situations, you would just call the virtual method normally and let polymorphism do its work to call the most-derived implementation as needed. What the author is trying to explain is that it is also possible to directly call a specific implementation of a virtual method without going through polymorphism.
Say a class derives from Fibonacci to override gen_elems() again, but you do not want to call that override, you want to call the Fibonacci override. By converting a num_sequence pointer into a Fibonacci pointer (or descendant pointer) at compile-time, it allows the compiler to access the Fiboncci vtable so it can emit code to directly call Fibonacci::gen_elems() (if the object being pointed at during run-time is not actually an instance of Fibanocci or descendant, you will likely crash/corrupt your app. That cannot be validated at compile-time).
For example:
class num_sequence
{
public:
virtual void gen_elems(int value)
{
std::cout << "num_sequence" << std::endl;
}
};
class Fibonacci : public num_sequence
{
public:
void gen_elems(int value)
{
std::cout << "Fibonacci" << std::endl;
}
};
class SomethingElse : public Fibonacci
{
public:
void gen_elems(int value)
{
std::cout << "SomethingElse" << std::endl;
}
};
.
num_sequence ns;
Fibonacci fib;
SomethingElse se;
num_sequence *ps;
ps = &ns;
ps->gen_elems(64); // displays "num_sequence"
ps = &fib;
ps->gen_elems(64); // displays "Fibonacci"
ps = &se;
ps->gen_elems(64); // displays "SomethingElse"
ps->Fiboacci::gen_elems(64); // compiler error!
static_cast<Fibonacci*>(ps)->Fibonacci::gen_elems(64); // displays "Fibonacci"
static_cast<SomethingElse*>(ps)->Fibonacci::gen_elems(64); // displays "Fibonacci"
Fibonacci *pfib = dynamic_cast<Fibonacci*>(ps);
if (pfib != NULL)
{
pfib->gen_elems(64); // displays "SomethingElse"
pfib->Fibonacci::gen_elems(64); // displays "Fibonacci"
}

Static ctor/dtor observer for arb. C++ classes

I have a series of classes A, B, ... which have many derived classes which are created inside a module I do not wish to change.
Additionally, I have at least one class Z, which has to be informed whenever an object of type A (or derived classes) is created or destroyed. In the future, there may be more classes, Y, X that want to observe different objects.
I am looking for a convenient way to solve this.
At first glance, the problem seemed trivial, but I'm kind of stuck right now.
What I came up with, is two base classes SpawnObserver and SpawnObservable which are supposed to do the job, but I am very unhappy with them for several reasons (see attached simplification of these classes).
When Z is notified, the actual object is either not yet or not anymore existent, due to the order in which base classes are created/destroyed. Although the pointers can be compared when destroying an object (to remove them from some data-structures in Z) this does not work when it is created and it surely does not work when you have multiple inheritance.
If you want to observe only one class, say A, you are always notified of all (A, B, ...).
You have to explicitly if/else through all classes, so you have to know all classes that inherit from SpawnObservable, which is pretty bad.
Here are the classes, which I tried to trim down to the most basic functionality, which you need to know to understand my problem. In a nutshell: You simply inherit from SpawnObservable and the ctor/dtor does the job of notifying the observers (well, at least, this is what I want to have).
#include <list>
#include <iostream>
class SpawnObservable;
class SpawnObserver {
public:
virtual void ctord(SpawnObservable*) = 0;
virtual void dtord(SpawnObservable*) = 0;
};
class SpawnObservable {
public:
static std::list<SpawnObserver*> obs;
SpawnObservable() {
for (std::list<SpawnObserver*>::iterator it = obs.begin(), end = obs.end(); it != end; ++it) {
(*it)->ctord(this);
}
}
~SpawnObservable() {
for (std::list<SpawnObserver*>::iterator it = obs.begin(), end = obs.end(); it != end; ++it) {
(*it)->dtord(this);
}
}
virtual void foo() {} // XXX: very nasty dummy virtual function
};
std::list<SpawnObserver*> SpawnObservable::obs;
struct Dummy {
int i;
Dummy() : i(13) {}
};
class A : public SpawnObservable {
public:
Dummy d;
A() : SpawnObservable() {
d.i = 23;
}
A(int i) : SpawnObservable() {
d.i = i;
}
};
class B : public SpawnObservable {
public:
B() { std::cout << "making B" << std::endl;}
~B() { std::cout << "killing B" << std::endl;}
};
class PrintSO : public SpawnObserver { // <-- Z
void print(std::string prefix, SpawnObservable* so) {
if (dynamic_cast<A*>(so)) {
std::cout << prefix << so << " " << "A: " << (dynamic_cast<A*>(so))->d.i << std::endl;
} else if (dynamic_cast<B*>(so)) {
std::cout << prefix << so << " " << "B: " << std::endl;
} else {
std::cout << prefix << so << " " << "unknown" << std::endl;
}
}
virtual void ctord(SpawnObservable* so) {
print(std::string("[ctord] "),so);
}
virtual void dtord(SpawnObservable* so) {
print(std::string("[dtord] "),so);
}
};
int main(int argc, char** argv) {
PrintSO pso;
A::obs.push_back(&pso);
B* pb;
{
std::cout << "entering scope 1" << std::endl;
A a(33);
A a2(34);
B b;
std::cout << "adresses: " << &a << ", " << &a2 << ", " << &b << std::endl;
std::cout << "leaving scope 1" << std::endl;
}
{
std::cout << "entering scope 1" << std::endl;
A a;
A a2(35);
std::cout << "adresses: " << &a << ", " << &a2 << std::endl;
std::cout << "leaving scope 1" << std::endl;
}
return 1;
}
The output is:
entering scope 1
[ctord] 0x7fff1113c640 unknown
[ctord] 0x7fff1113c650 unknown
[ctord] 0x7fff1113c660 unknown
making B
adresses: 0x7fff1113c640, 0x7fff1113c650, 0x7fff1113c660
leaving scope 1
killing B
[dtord] 0x7fff1113c660 unknown
[dtord] 0x7fff1113c650 unknown
[dtord] 0x7fff1113c640 unknown
entering scope 1
[ctord] 0x7fff1113c650 unknown
[ctord] 0x7fff1113c640 unknown
adresses: 0x7fff1113c650, 0x7fff1113c640
leaving scope 1
[dtord] 0x7fff1113c640 unknown
[dtord] 0x7fff1113c650 unknown
I want to stress, that I am perfectly aware why my solution behaves the way it does. My question is whether you have a better approach of doing this.
EDIT
As an extension to this question (and inspired by the comments below), I'd like to know:
Why do you think this is a terrible approach?
As an additional note: What I an trying to accomplish by this is to install a normal Observer in each and every created object.
EDIT 2
I will accept an answer that solves problem 1 (bold one in the enumeration above) or describes why the whole thing is a very bad idea.
Use the curiously recurring template pattern.
template<typename T> class watcher {
typename std::list<T>::iterator it;
watcher();
~watcher();
void ctord(T*);
void dtord(T*);
};
template<typename T> class Observer {
public:
typedef std::list<T*> ptr_list;
static ptr_list ptrlist;
typedef typename ptr_list::iterator it_type;
it_type it;
typedef std::list<watcher<T>*> watcher_list;
static watcher_list watcherlist;
typedef typename watcher_list::iterator watcher_it_type;
Observer() {
ptrlist.push_back(this);
it_type end = ptrlist.end();
end--;
it = end;
for(watcher_it_type w_it = watcherlist.begin(); w_it != watcherlist.end(); w_it++)
w_it->ctord(this);
}
~Observer() {
ptrlist.erase(it);
for(watcher_it_type w_it = watcherlist.begin(); w_it != watcherlist.end(); w_it++)
w_it->ctord(this);
}
};
class A : public Observer<A> {
};
class B : public Observer<B> {
};
class C : public A, public B, public Observer<C> {
// No virtual inheritance required - all the Observers are a different type.
};
template<typename T> watcher<T>::watcher<T>() {
Observer<T>::watcherlist.push_back(this);
it = watcherlist.end();
it--;
}
template<typename T> watcher<T>::~watcher<T>() {
Observer<T>::watcherlist.erase(it);
}
template<typename T> void watcher<T>::ctord(T* ptr) {
// ptr points to an instance of T that just got constructed
}
template<typename T> void watcher<T>::dtord(T* ptr) {
// ptr points to an instance of T that is just about to get destructed.
}
Not just that, but you can inherit from Observer multiple times using this technique, as two Observer<X> and Observer<Y> are different types and thus doesn't require diamond inheritance or anything like that. Plus, if you need different functionality for Observer<X> and Observer<Y>, you can specialize.
Edit # Comments:
class C DOES inherit from Observer<A> and Observer<B> through A and B, respectively. It doesn't need to know or care whether or not they're being observed. A C instance will end up on all three lists.
As for ctord and dtord, I don't actually see what function they perform. You can obtain a list of any specific type using Observer::ptrlist.
Edit again: Oooooh, I see. Excuse me a moment while I edit some more. Man, this is some of the most hideous code I've ever written. You should seriously consider not needing it. Why not just have the objects that need to be informed about the others do their creation?
Issue 1 isn't easily solved (in fact I think it's impossible to fix). The curiously recurring template idea comes closest to solving it, because the base class encodes the derived type, but you'll have to add a base to every derived class, if you really insist on knowing the derived type when the base is being constructed.
If you don't mind performing your actual operations (other than the bookkeeping, I mean) or examining the list outside the constructor or destructor of each object, you could have it (re)build the minimal list only when the operation is about to be performed. This gives you a chance to use the fully-constructed object, and makes it easier to solve issue 2.
You'd do this by first having a list of objects that have been constructed, but aren't on the 'full' list. And the 'full' list would contain two pointers per constructed object. One is the pointer to the base class, which you'll store from the Observable constructor, possibly multiple times during the construction of a single object. The other is a void *, pointing to the most derived part of the object -- use dynamic_cast<void *> to retrieve this -- and is used to make sure that each object only appears once in the list.
When an object is destroyed, if it has multiple Observable bases, each will try to remove itself from the lists, and when it comes to the full list, only one will succeed -- but that's fine, because each is equally good as an arbitrary base of that object.
Some code follows.
Your full list of objects, iterable in as straightforward a fashion as std::map will allow. (Each void * and each Observable * is unique, but this uses the Observable * as the key, so that it's easy to remove the entry in the Observable destructor.)
typedef std::map<Observable *, void *> AllObjects;
AllObjects allObjects;
And your list of objects that have been constructed, but aren't yet added to allObjects:
std::set<Observable *> recentlyConstructedObjects;
In the Observable constructor, add the new object to the list of pending objects:
recentlyConstructedObjects.insert(this);
In the Observable destructor, remove the object:
// 'this' may not be a valid key, if the object is in 'allObjects'.
recentlyConstructedObjects.erase(this);
// 'this' may not be a valid key, if the object is in 'recentlyConstructedObjects',
// or this object has another Observable base object and that one got used instead.
allObjects.erase(this);
Before you're about to do your thing, update allObjects, if there've been any objects constructed since last time it was updated:
if(!recentlyConstructedObjects.empty()) {
std::map<void *, Observable *> newObjects;
for(std::set<Observable *>::const_iterator it = recentlyConstructedObjects.begin(); it != recentlyConstructedObjects.end(); ++it)
allObjectsRev[dynamic_cast<void *>(*it)] = *it;
for(std::map<void *, Observable *>::const_iterator it = newObjects.begin(); it != newObjects.end(); ++it)
allObjects[it->second] = it->first;
recentlyConstructedObjects.clear();
}
And now you can visit each object the once:
for(std::map<Observable *,void *>::const_iterator it = allObjects.begin(); it != allObjects.end(); ++it) {
// you can dynamic_cast<whatever *>(it->first) to see what type of thing it is
//
// it->second is good as a key, uniquely identifying the object
}
Well... now that I've written all that, I'm not sure whether this solves your problem. It was interesting to consider nonetheless.
(This idea would solve one of the problems with the curiously recurring template, namely that you have lots of base objects per derived object and it's harder to disentangle because of that. (Unfortunately, no solution to the large number of base classes, sorry.) Due to the use of dynamic_cast, of course, it's not much use if you call it during an object's construction, which is of course the advantage of the curiously recurring thing: you know the derived type during the base's construction.
(So, if your'e going with that style of solution, AND you are OK with performing your operations outside the construction/destruction stage, AND you don't mind the (multiple) base classes taking up space, you could perhaps have each base's constructor store some class-specific info -- using typeid, perhaps, or traits -- and merge these together when you build the larger list. This should be straightforward, since you'll know which base objects correspond to the same derived object. Depending on what you're trying to do, this might help you with issue 3.)
Take a look at Signals and Slots especially Boost Signals and Slots