Advertisement

Console Menu Structure - How to get node type

Started by July 02, 2019 09:04 PM
30 comments, last by Geosesarma 5 years, 5 months ago

I tried to edit the post, but the editing option expired. So here's the new version.

Spoiler

MenuItem.h



class MenuItem
{
	int type_ID{0};
public:
	int getType_ID();

	MenuItem(int type){type_ID=type};
};

SubmenuItem.h



#include "MenuItem.h"
#include <vector>

class SubmenuItem
{
	std::vector<MenuItem*> menu_addresses;
  	int fixed_set{0};
  	int current_index{o};
public:
  	void add(const MenuItem* set);
  
	MenuItem* getAdress(int index)
	{
		return menu_addresses[index-1];
	}
  
  	int getSubMenuSize()
  	{
  		return static_cast<int>(manu_addresses.size());
  	}
};

SubmenuItem.cpp



#include "SubmenuItem.h"
  
void SubmenuItem::add(const MenuItem* set)
{
	menu_addresses.push_back(set); 
  	setType();
}

MenuNavigator.h




#include "SubmenuItem.h"
  
class MenuNavigator
{
public:
	//SubmenuItem iterator

    enum class MenuNavigator::subTypes
    {
    	action_call=1,
		root=2,
		//...
    }

	//possible applications:
  	
	int findFirstType(SubmenuItem& loop_box, int type_wanted)
	{
		//finds the first of a type

		for(int iterator{1};iterator<=loop_box.getSubMenuSize();iterator+=1)
		{
        	if (type_wanted == static_cast<int>(loop_box.getAddress(iterator)->getType()))
          	{
          		return iterator;
          	}
        }
		
		return 0;
	}

  	int findAllOfSameType(SubmenuItem& loop_box, int type_wanted)
	{
		//finds all of the same type, stops after finding 5th menu item
	
		int results{0};
		
		for(int iterator{1};(iterator<=loop_box.getSubMenuSize() && iterator <= 5);iterator+=1)
		{
        	results+=1;
		}             
		
		return results;
	}
};

main.cpp



#include "MenuNavigator.h"
#include <iostream>

std::string getNodeType(int type_wanted)
{
 	if (static_cast<MenuNavigator::subTypes>(type_wanted) == MenuNavigator::subTypes::root)
  	{
  		return "root";
   	}
  
 	if (static_cast<MenuNavigator::subTypes>(type_wanted) == MenuNavigator::subTypes::action_call)
  	{
  		return "action call";
   	}
}
 
int main()
{
	MenuItem a{1};
	MenuItem b{1};
	MenuItem c{2};

	SubmenuItem D;
		
	D.add(&a);
	D.add(&b);
	D.add(&c);

	MenuNavigator E;

	//possible use:	

	//the 1 here means action call
	int first_index{ E.loop_findFirstType(D, 1)};		
	
	//here it means finding the node type
	//this should return 1(searching index result) as MenuItem
	
	//a is the first added MenuItem which has type 1  	
  	
  	//after indefying the first MenuItem of action call type index,
  	//iterate through the Submenu D
  
	std::cout << getNodeType(D.getAdress(1)->getType());

 

 

I probably just programmed the manual dynamic_cast version in the most cumbersome way possible but at least its a starting point for my brain to work with. Thanks anyway Acosix and of course all the others who helped me.

(Not tested, no production code!)

 


class MenuItem {
private:
	size_t typeID;
	std::string name;
	static size_t generateID() { static size_t id; return id++; }
public:
	MenuItem() : typeID(0), name("") {}

	template <typename T>
	static size_t getTypeID() {
		static size_t id = generateID();
		return id;
	}

	void setName(const std::string& n) { name = n; }
	const std::string& getName() const { return name; }

	void setID(size_t id) { typeID = id; }
	size_t getID() { return typeID; }
};

class ActionItem : public MenuItem {
public:
	ActionItem(const std::string& displayName) {
		setName(displayName);
		setID(MenuItem::getTypeID<ActionItem>());
	}
};

class SubmenuItem : public MenuItem {

private:
	std::vector<MenuItem*> childItems;
	MenuItem* findRec(const std::string& name, std::vector<MenuItem*>& childs) {

		for (auto& child : childs)
		{
			if (child->getName() == name)
				return child;

			if (child->getID() == MenuItem::getTypeID<SubmenuItem>())
			{
				SubmenuItem* sub = static_cast<SubmenuItem*>(child);
				if (sub->childItems.size() <= 0)
					return nullptr;
				else
					return findRec(name, sub->childItems);
			}
		}
		return nullptr;
	}
public:
	SubmenuItem(const std::string& displayName) {
		setName(displayName);
		setID(MenuItem::getTypeID<SubmenuItem>());
	}

	template <typename T>
	T* get(const std::string& whatIwant)
	{
		MenuItem* temp = findRec(whatIwant, childItems);
	
		if (temp && temp->getID() == MenuItem::getTypeID<T>())
			return static_cast<T*>(temp);
		else
			return nullptr;
	}

	void add(MenuItem * item)
	{
		childItems.push_back(item);
	}

	//Only for testing
	void print() {
		cout << "=====================================" << endl;
		cout << "childItems from -- " << getName() << " -- " << endl;
		cout << "=====================================" << endl;
		for (const auto& m : childItems)
		{
			cout << m->getName() << endl;
		}
		cout << "=====================================" << endl;
	}
};

 

Advertisement

Top code, man. But do you have any questions regarding it; or any other which could've arose by newest posts?


class SubmenuItem : public MenuItem {

private:
	std::vector<MenuItem*> childItems;

//...

Why do you have a pointer to the base class?

This can soon lead to recursivity errors.

This would work:


class A
{
	int Ac;
};

class B
{
	A* a;
};

This would throw recursive errors however.


class A
{
	B b1;
};

class B
{
	A* a1;
};

You can't have two classes point at each other, even if they
are defined in separate header files. Recursive code is
error-prone as it in this case means you make define the
same class twice.

Also you use the #include file pre-processor command,
which means there are multiple of header compiled
definitions, leading to even more errors.

A.h


#pragma once

#include "B.h"

class A
{
	B b1;
};

B.h


#pragma once

#include "A.h"

class B
{
	A* a1;
};

#pragma once is a command for linker and compiler not to
define the same header multiple times. Recursive definitions
bypass this command.

Same goes for inheritance - SubmenuItem and MenuItem
are two separate classes.

I'm not showing you this as how to code in C++, I'm just
saying once you get into above problem, you'll run
into a very large amount of errors. Even though you know
now ( or you have learnt before ), this can lead to
very hard to fix errors.

Like I've mentioned before, SubmenuItem Is a container for
the MenuItem instantiations. I showed how to do this in
my last previous post.

SubmenuItem should not be a derived class of MenuItem.
Polymorphism is an extension of inheritance. It uses
the definition of inheritance as "is-a".

A derived class is-a of base class, for example:
you have a base class Fruit and a derived class Apple.
An apple is a fruit.

Quote

A SubmenuItem is just a container which contains other "MenuItems".

In your case a SubmenuItem would be a MenuItem. if I
understood your code correctly, it isn't. A SubmenuItem
is a container for the abstract MenuItem instantiations.

27 minutes ago, Acosix said:

Why do you have a pointer to the base class?

This can soon lead to recursivity errors.

You can check out the composite pattern. Because MenuItem is the base class I have to use a pointer to the base class in order to store any "MenuItem" in it....

28 minutes ago, Acosix said:

You can't have two classes point at each other, even if they
are defined in separate header files

Sure you can  forward declare your class and use a pointer.


class B;
class A {
public:
	B* b;
}

class B {
public:
  A* a;
}

 

36 minutes ago, Acosix said:

In your case a SubmenuItem would be a MenuItem. if I
understood your code correctly, it isn't. A SubmenuItem
is a container for the abstract MenuItem instantiations.

Yes you are correct a SubmenuItem is a container for MenuItems but a SubmenuItem can also contain other SubmenuItems.

Therefore it would (in my case) be pointless to not inherit from MenuItem. Especially with the type system in place.

48 minutes ago, Acosix said:

You can't have two classes point at each other, even if they
are defined in separate header files.

This is plain wrong.

The keyword here is forward declaration:


class B;

class A
{
public:
    B* mB;
};

class B
{
public:
    A* mA;
};

int main(int, char**)
{
    A myA;
    B myB;

    myA.mB = &myB;
    myB.mA = &myA;
}

Compiles just fine. A pointer is just an address and does not need any information about a class, apart from that it exists. Problems with ring dependencies start when it comes to dynamic memory allocation and ownership issues, but there are special smart pointers for that in the STL: shared_ptr & weak_ptr.

As far as I know, a lot of GUI libraries use such mechanisms under the hood to traverse the dependency trees.

Greetings

 

EDIT: ninja'd by @Geosesarma ?

 

Advertisement

I won't argue with you. It would just go off-topic. I could explain why forward declaration is bad in my opinion, but trying to convince others I could be right has always got me into flaming discussions.

5 minutes ago, Acosix said:

... I could explain why forward declaration is bad in my opinion ...

I have no problem with the "contract" principle, in this case the contract to provide an implementation when i feel it's time to do so. As long as the compiler fulfils its part ;-)

C++ lets you use it the way you want, without knocking on your fingers every time you do something that might need sort of a supervision. More freedom. Other languages fit tighter. All have their purpose.

18 minutes ago, Green_Baron said:

I have no problem with the "contract" principle, in this case the contract to provide an implementation when i feel it's time to do so. As long as the compiler fulfils its part ?

C++ lets you use it the way you want, without knocking on your fingers every time you do something that might need sort of a supervision. More freedom. Other languages fit tighter. All have their purpose.

Thanks for clarification matey - what regards the flexibility of C++.

I have been  trying to say, the forward declaration is off-topic. You mention to the OP they should consider forward declaration. If they like it or want to use, it's their way - regardless this is a Beginner's Public Group.

Quote

Console Menu Structure - How to get node type

This is the name of the topic, not forward declaration.

53 minutes ago, Acosix said:

I have been  trying to say, the forward declaration is off-topic. You mention to the OP they should consider forward declaration. If they like it or want to use, it's their way - regardless this is a Beginner's Public Group.

 

1

 

I don't think it's off-topic if it is part of a potential solution (as it seems to be). You said, two classes pointing at each other is not possible, which is not true.

@Geosesarma and I provided code that showed the opposite. As I said before, to my knowledge child and parent keeping pointers to each other is a common pattern in GUI libraries.

As @Green_Baron stated, C++ let you do things in many different ways. That's its big strength and also its biggest flaw since it doesn't stop you from programming yourself into a corner. If you just wanted to say, that the pointer approach can get you into trouble then you are right. But as long as you know its problems (and also how to handle them) and have a good reason to use it (I think he does), it is a valid solution.

 

Greetings

 

 

This topic is closed to new replies.

Advertisement