Home > OS >  Supporting custom hooks in existing cpp class
Supporting custom hooks in existing cpp class

Time:06-08

I am designing support for custom hooks in existing C class.

class NotMyClass {
  public: 
    void DoSomething() {
      // Needs custom logic here.
      hook_.DoSomethingCustom();
    }

  protected:
    Hook hook_;
    int not_my_class_inner_variable_1_;
    Node not_my_class_inner_variable_2_;
    ...... More Class vars.....
}

class Hook {
  public: 
    void DoSomethingCustom() {
    // Some custom logic that needs to access not_my_class_inner_variable_1_, not_my_class_inner_variable_2 etc. .
    }
}

Adding some more context here after initial comments: NotMyClass class is autogenerated and no custom logic can be added to this class. We want to be able to add custom hooks inside the autogenerated classes. So the plan was to instead pass/ingect in a Hook class that will be able to provide some custom processing. The autogenerated NotMyClass class will have hook_. DoSomethingCustom().

What's the best way to access NotMyClass member variables inside Hook ?

I don't want to change the class structure(that is use inheritence) of NotMyClass due to additional constraints.

Is making Hook a friend of NotMyClass a good option and then passing NotMyClass as this to Hook functions ?

Thanks in advance!

CodePudding user response:

The problem cannot be solved as stated, i.e., without breaking the Open-Closed-Principle (OCP), which says that "classes (and other things) should be open for extension but closed for modification." In this case, this means that you shouldn't try to both (a) leave MyClass unchanged and (b) access its private or protected members from outside. Private (or protected) signal things that are not accessed from the outside, that's literally what private (or protected) are designed for. You can circumvent this (old ways, new ways) but you shouldn't.

The answer by sanitizedUser modifies MyClass, which is undesirable as per the question. A hacky but straight-forward suggestion to your problem might be to pass the fields to be modified explicitly to the method by reference:

class MyClass {
  public: 
    void DoSomething() {
      // Pass references to the fields you want to modify.
      hook_.DoSomethingCustom(my_class_inner_variable_1_, my_class_inner_variable_2_);
    }

  protected:
    Hook hook_;
    int my_class_inner_variable_1_;
    Node my_class_inner_variable_2_;
}

class Hook {
  public: 
    void DoSomethingCustom(int &inner_variable_1, Node& inner_variable_2_) {
      // Use the data members.
    }
}

To signal that your Hook class explicitly is allowed to access members of MyClass, you could declare it as a friend. Example:

#include <iostream>

class Node {};

class MyClass;

class Hook {
  public: 
    void DoSomethingCustom(MyClass &m);
};

class MyClass {
    friend Hook;  // Allows the Hook class to access our members!

  public: 
    MyClass(Hook h): hook_(h) {}

    void DoSomething() {
      // Pass references to the fields you want to modify.
      hook_.DoSomethingCustom(*this);
    }

    void print_my_class_inner_variable_1_() {
        std::cout << my_class_inner_variable_1_ << std::endl; 
    }

  protected:
    Hook hook_;
    int my_class_inner_variable_1_;
    Node my_class_inner_variable_2_;
};

void Hook::DoSomethingCustom(MyClass &m) {
      // Allowed to access private member because we are a friend!
      m.my_class_inner_variable_1_ = 42;
}

int main() {
    MyClass c{Hook{}};
    c.print_my_class_inner_variable_1_();
    c.DoSomething();
    c.print_my_class_inner_variable_1_();
}

Note: your whole design with this "Hook" looks very weird to me. How do you "add hooks" to this thing (which imho is one of the defining requirements for calling something a "hook")? I'm sure if you posted a lot more context, people here would suggest a very different larger-scale design.

CodePudding user response:

It's not an ideal solution but if you are allowed to declare the Hook class a friend of NotMyClass then the following code somewhat works.

#include <iostream>

class NotMyClass;

class Hook {
  public: 
    void DoSomethingCustom(const NotMyClass& c);
};

class NotMyClass {
  friend Hook;
  
  public:
    void DoSomething() {
      hook_.DoSomethingCustom(*this);
    }

  protected:
    Hook hook_;
    int not_my_class_inner_variable_1_;
    // Commenting Node member out because the definition of it is missing.
    // Node not_my_class_inner_variable_2_;
};

void Hook::DoSomethingCustom(const NotMyClass& c) {
    std::cout << c.not_my_class_inner_variable_1_ << '\n';
}

int main() {
    NotMyClass{}.DoSomething();
    return 0;
}

Output.

0

If you can modify NotMyClass entirely then I advice you to use polymorphism and declare Hook as an abstract class. This way its behaviour can be swapped more easily.

#include <iostream>
#include <string>

template<class State>
struct Hook {
    virtual State run(const State& s) const = 0;
};

struct ExampleState {
    int number;
    std::string text;
};

std::ostream& operator<<(std::ostream& stream, const ExampleState& state) {
    stream << state.number << ", " << state.text << '\n';
    return stream;
}

struct ExampleHook : public Hook<ExampleState> {
    ExampleState run(const ExampleState& s) const override;
};

class Receiver {
    public:
        void DoSomething();
        Receiver(const Hook<ExampleState>* const hook);
    
    private:
        const Hook<ExampleState>* const hook;
        ExampleState state;
};

ExampleState ExampleHook::run(const ExampleState& s) const {
    // Returning a modified state.
    return {
        s.number   1,
        "Modified "   s.text
    };
}

void Receiver::DoSomething() {
    std::cout << "Original state:\n" << this->state;
    this->state = this->hook->run(this->state);
    std::cout << "Modified state:\n" << this->state;
}

Receiver::Receiver(const Hook<ExampleState>* const hook)
    : hook(hook), state{0, "hello"} {}

int main() {
    ExampleHook hook;
    Receiver receiver(&hook);
    receiver.DoSomething();
    return 0;
}

Output.

Original state:
0, hello
Modified state:
1, Modified hello

CodePudding user response:

One way of doing this is declaring data members of class MyClass public and then passing a reference to an instance of MyClass to an instance of Hook.

class MyClass {
  public: 
    void DoSomething() {
      // Pass a reference to this.
      hook_.DoSomethingCustom(*this);
    }

  public:
    Hook hook_;
    int my_class_inner_variable_1_;
    Node my_class_inner_variable_2_;
}

class Hook {
  public: 
    void DoSomethingCustom(const MyClass& c) {
      // Use the data members.
      auto& ref1 = c.my_class_inner_variable_1_;
      auto& ref2 = c.my_class_inner_variable_2_;
    }
}

If you cannot declare the members public because this is a legacy code, then there is always the evil option.

#define protected public

// Your code.

#undef protected

However, if this code is already compiled as a dynamic library then you are out of luck.

  •  Tags:  
  • c
  • Related