Parameters of a virtual function that are not used in all subclasses; any better way to design?

I am writing several related C ++ classes and I am having some problems designing a specific inherited function.

In particular, classes are all “operations” on trees, and I need to be able to perform a set of arbitrary tree actions on the tree. I have a function called ExecuteOperation () in each class.

The problem is that in some classes I need more information than in others. Right now, I'm just passing extra information to all classes, with a base base class defined as follows:

class BasicOperation { public: //... virtual void ExecuteOperation(Tree* tree, multimap<int,Foo*> extra1, multimap<int,Foo*> extra2) = 0; //... } 

The descendants of the BasicOperation subclass, "SpecialOperation", need the extra1 and extra2 parameters, and the descendants of the "NonspecialOperation" don't use these parameters at all.

An ExecuteOperation subscriber usually looks something like this:

 Tree* tree; std::vector<BasicOperation*> operations; //... for(size_t i=0;i<operations.size();i++) { operations[i]->ExecuteOperation(tree,extra1,extra2); } 

Filling extra1 and extra2 depends on a particular tree and can be intensive computation (it is better not to recreate them inside each call to ExecuteOperation).

Is there a better way to design this so that only SpecialOperation objects receive the parameters passed to them? Or is it usually just fine to use unused parameters for classes like NonspecialOperation?

The definition of NonspecialOperation :: ExecuteOperation () with two parameters that it does not use is strange.

The only thing I've been thinking so far is to provide SpecialOperation objects with each pointer back to the caller and store additional information in the caller's object. I do not really like this solution, because extra1 and extra2 are too dependent on the current state; problems with parallelization that the installation simply “feels” wrong.

In addition, wrapping the tree, extra1 and extra2 in another structure to go into ExecuteOperation can make it cleaner and makes sense, because extra1 and extra2 are additional descriptors for the tree, but I don't know if this is the best solution

 struct TreeEx { Tree* tree; multimap<int,Foo*> extra1, extra2; }; //in BasicOperation: virtual void ExecuteOperation(TreeEx* tree_with_info) = 0; 
+4
source share
2 answers

Using

virtual void ExecuteOperation (tree of trees);

No additional arguments.

Pass additional arguments through the constructor in classes derived from BasicOperation . Add methods to the tree that allow BasicOperation to retrieve the required data.

Also see Command pattern and Visitor pattern.

- EDIT -

Additional arguments are parts of the tree.

Make a virtual function in the base class of the tree that creates the desired command

 class Tree{ public: virtual BasicOperation* makeSpecificTreeOperation() = 0; virtual ~Tree(); }; 
  • for each type of team.

Discard it in child

  class DerivedTree: public Tree{ ... virtual BasicOperation* makeSpecificTreeOperation(); ... }; class DerivedCommand: public BasicOperation{ .... public: DerivedCommand(ExtraTreeData& extraData); }; BasicOperation* DerivedTree::maksSpecificTreeOperation(){ return new DerivedCommand(this->extraData); } 

and return the required command class by passing data to the command using the constructor.

See Abstract Factory and Builder Template .

+3
source

Clear to return the link to the caller if it is through the interface. That way you invert the dependency. You will have:

virtual void ExecuteOperation(ICaller* caller)

You can then either implement it in the actual caller (but you apparently have concurrency problems in your context) or implement it in a dedicated class. This way you reduce grip, which is good. Of course, ICaller provides getters for extra1 and extra2 , which will only be called for the corresponding BasicOperation implementations.

+1
source

Source: https://habr.com/ru/post/1401426/


All Articles