How to add a checking function before calling a function in a class? It's like a pre-function or base function or something like that.

Manda Rajo 141 Reputation points
2022-03-16T08:45:01.37+00:00

The questions are placed in the comments in the code below. I tried to use 'virtual' but it's not the solution of my problem, so is there another solution? Thanks.

class Item {
public:
};

class Layout {
public:
    std::vector<Item *> items;
    // ...
};

class ClassBase {
public:
    Layout layout;
    virtual void draw() {
        if (layout.items.size()) {
            printf("Warning: The layout must be cleared before calling the function draw.\n");
            printf("The reason is two objects with the same ClassBase can have different layouts.\n");
        }
    }
};

#define CHECK

class MyClass : public ClassBase {
public:
    void draw() {
        CHECK; // <-- Adding a MACRO in the beginning of the function like this is annoying because there are a lot of draw functions,
               //     like 1000 draw functions across the whole project, and imagining repeating this CHECK, and if I forget?
        printf("DRAW\n");
    }
};

int main()
{
    MyClass c;
    c.layout.items.push_back(new Item); // <-- Add an item in the layout.
    c.draw(); // <-- It doesn't show a warning but it must show the warning in ClassBase::draw because the layout is not cleared.

    _getch();
    return 0;
}

Example of real use in my project

// The pre-function can be extremely complex:
#define EMC_IDNAME() \
    { \
        bool doesExist = !!findClass(std::string(__func__)); \
        emc_idName = __func__; /* This must be after the above. */ \
        if (doesExist) { /* The reason this checking is placed here instead of inside 'addClassToList' is the idName is still "" inside that function. One of the reasons of that checking is to avoid a registering inside an internal source code by using of 'new'. */ \
            EMC_ERROR((std::string("The class '") + emc_idName + "' is already registered.").c_str()); \
        } \
    } ((void)0) // There must be ";".


emc::VIEW3D_HT_Header::VIEW3D_HT_Header() : ClassBase(ClsType_Header) {
    EMC_IDNAME(); // <-- See, here is an example of a pre-function, it's repeated like 1000 times accross the whole project.
}
void emc::VIEW3D_HT_Header::draw() { // This currently doesn't have a pre-function, but making draw() : draw_base() is same as adding a checking in the beginning.
    layout.editorType();
    layout.prop(TYPE_TO_STRING(VIEW3D_MT_Mode));
    layout.menu(TYPE_TO_STRING(VIEW3D_MT_EditorMenus));
}

emc::VIEW3D_MT_EditorMenus::VIEW3D_MT_EditorMenus() : ClassBase(ClsType_Menu) {
    EMC_IDNAME(); // <-- See, here is an example of a pre-function, it's repeated like 1000 times accross the whole project.
}
void emc::VIEW3D_MT_EditorMenus::draw() {
    layout.menu(TYPE_TO_STRING(VIEW3D_MT_View));
    layout.menu(TYPE_TO_STRING(VIEW3D_MT_Add));
}

//----------------------------------------------------------------------------------------------------

emc::VIEW3D_MT_Mode::VIEW3D_MT_Mode() : ClassBase(ClsType_Menu) {
    EMC_IDNAME(); // <-- See, here is an example of a pre-function, it's repeated like 1000 times accross the whole project.
    emc_label = "[end], Mode";
}
void emc::VIEW3D_MT_Mode::draw() { // This currently doesn't have a pre-function, but making draw() : draw_base() is same as adding a checking in the beginning.
    layout.op(TYPE_TO_STRING(VIEW3D_OT_Mode_ObjectMode));
    layout.op(TYPE_TO_STRING(VIEW3D_OT_Mode_EditMode));
    layout.op(TYPE_TO_STRING(VIEW3D_OT_Mode_SculptMode));
    layout.op(TYPE_TO_STRING(VIEW3D_OT_Mode_VertexPaint));
    layout.op(TYPE_TO_STRING(VIEW3D_OT_Mode_WeightPaint));
    layout.op(TYPE_TO_STRING(VIEW3D_OT_Mode_TexturePaint));
}
Developer technologies C++
0 comments No comments
{count} votes

Accepted answer
  1. Igor Tandetnik 1,116 Reputation points
    2022-03-16T14:27:01.667+00:00

    One common approach goes like this:

    class ClassBase {
     public:
      void draw() {
        CheckPrecondition();
        do_draw();
      }
     protected:
      virtual void do_draw() = 0;
    };
    
    class MyClass : public ClassBase {
     protected:
      void do_draw() override {
        // Actual drawing here
      }
    };
    

    This allows the base class to do whatever it deems necessary before and after delegating to derived classes. This is an instance of the template method pattern (the word "template" here is unrelated to C++ templates).

    0 comments No comments

4 additional answers

Sort by: Most helpful
  1. David Lowndes 4,726 Reputation points
    2022-03-16T09:32:27.737+00:00

    Rather than having to add code to each method, have you considered designing out the problem (as much as possible)?
    See this discussion on Raymond's blog.

    1 person found this answer helpful.
    0 comments No comments

  2. YujianYao-MSFT 4,296 Reputation points Microsoft External Staff
    2022-03-16T09:37:10.81+00:00

    Hi @Manda Rajo ,

    I could offer an idea:

    Delete the draw() in the subclass, and delete the virtual keyword of the draw() in the base class. Then write a bool check() function for checking, change the draw() in the base class to draw(bool a) and add the operations you want to do in check(). The sample code is as follows:

    class Item {  
    public:  
    };  
    class Layout {  
    public:  
        std::vector<Item*> items;  
        // ...  
    };  
    class ClassBase {  
    public:  
        Layout layout;  
        bool check();  
        void draw(bool a) {  
            if (layout.items.size()) {  
                printf("Warning: The layout must be cleared before calling the function draw.\n");  
                printf("The reason is two objects with the same ClassBase can have different layouts.\n");  
            }  
        }  
    };  
    #define CHECK  
    class MyClass : public ClassBase {  
    public:  
          
    };  
    int main()  
    {  
        MyClass c;  
        c.layout.items.push_back(new Item); // <-- Add an item in the layout.  
        c.draw(c.check()); // <-- It doesn't show a warning but it must show the warning in ClassBase::draw because the layout is not cleared.  
        _getch();  
        return 0;  
    }  
    

    Best regards,

    Elya


    If the answer is the right solution, please click "Accept Answer" and upvote it.If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.
    0 comments No comments

  3. Manda Rajo 141 Reputation points
    2022-03-16T10:08:51.767+00:00

    Programming is so good, and I'm addictive to post the solution but posting too much annoys people, that's why goodbye.

    void draw_begin() {
        EMC_VERIFY(layout.items.size() == 0);
        draw();
    }
    void draw_end() {
        // Clear the layout because it's not needed anymore
        layout.items.clear();
    }
    
    // The warning is placed in one single function
    void internal_imguiDebug_classStack() {
        for (auto &cls : g_classStack) { // This is placed here because no need to expand a TreeNode to evaluate it.
            auto items = cls->layout.items;
            if (items.size() != 0) {
                static bool shown = false;
                if (!shown) {
                    shown = true;
                    EMC_ERROR_NOBREAK((std::string("Class Stack > ") + cls->emc_idName + ": A layout must always be cleared after the items are created. You didn't call " + FUNCTION_TO_STRING(ClassBase::draw_end) + ".").c_str());
                }
            }
        }
        ...
    }
    
    
    
    void Header::update_objs(int debug_displayFlags)
    {
        if (debug_displayFlags & 0x1) {
            printf("%s: Update objs.\n", _.cls->emc_idName.c_str());
        }
    
        _.cls->draw_begin(); // <-- BEGIN A DRAW **************************************************
        for (auto &it : _.cls->layout.items) {
            if (it.itemType == ItemType_EditorType) { // Editor Type
                EMC_VERIFY(_.editorType_menuComboBox == nullptr && "There's already an Editor Type.");
                auto &it1 = it;
                ClassBase *it1_cls = nullptr;
                EMC_FINDCLASS(it1_cls, TYPE_TO_STRING(EDITORTYPE_MT));
                MenuItem *menuItem = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                _.editorType_menuComboBox = menuItem;
            }
            else if (it.itemType == ItemType_Prop) { // Mode
                auto &it1 = it;
                ClassBase *it1_cls = nullptr;
                EMC_FINDCLASS(it1_cls, it1.idName);
                MenuItem *menuItem = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                _.mode_menuComboBox = menuItem;
            }
            else if (it.itemType == ItemType_Menu) { // Menu
                ClassBase *it_cls = nullptr;
                EMC_FINDCLASS(it_cls, it.idName);
                if (_.showMenus) {
                    it_cls->draw_begin(); // <-- BEGIN A DRAW **************************************************
                    for (auto &it1 : it_cls->layout.items) {
                        ClassBase *it1_cls = nullptr;
                        EMC_FINDCLASS(it1_cls, it1.idName);
                        MenuItem *menuItem = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                        _.menuItems.push_back(menuItem);
                    }
                    it_cls->draw_end(); // <-- END A DRAW **************************************************
                }
                else {
                    auto &it1 = it;
                    ClassBase *it1_cls = it_cls;
                    _.collapsedMenu = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                }
            }
            else {
                EMC_ERROR_WRONGTYPE();
            }
        }
        _.cls->draw_end(); // <-- END A DRAW **************************************************
    }
    
    0 comments No comments

  4. Manda Rajo 141 Reputation points
    2022-03-16T14:55:14.403+00:00

    IgorTandetnik-1300, thanks, and here is how I apply it on my real project:

    void draw_begin() {
        EMC_VERIFY(layout.items.size() == 0); // There is no problem but this VERIFY is almost never evaluated because the layout is always empty before calling draw.
        draw();
    }
    void draw_end() {
        // Clear the layout because it's not needed anymore
        layout.items.clear();
    }
    
    
    
    void Header::update_objs(int debug_displayFlags)
    {
        if (debug_displayFlags & 0x1) {
            printf("%s: Update objs.\n", _.cls->emc_idName.c_str());
        }
        _.cls->draw_begin(); // <-- BEGIN A DRAW **************************************************
        for (auto &it : _.cls->layout.items) {
            if (it.itemType == ItemType_EditorType) { // Editor Type
                EMC_VERIFY(_.editorType_menuComboBox == nullptr && "There's already an Editor Type.");
                auto &it1 = it;
                ClassBase *it1_cls = nullptr;
                EMC_FINDCLASS(it1_cls, TYPE_TO_STRING(EDITORTYPE_MT));
                MenuItem *menuItem = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                _.editorType_menuComboBox = menuItem;
            }
            else if (it.itemType == ItemType_Prop) { // Mode
                auto &it1 = it;
                ClassBase *it1_cls = nullptr;
                EMC_FINDCLASS(it1_cls, it1.idName);
                MenuItem *menuItem = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                _.mode_menuComboBox = menuItem;
            }
            else if (it.itemType == ItemType_Menu) { // Menu
                ClassBase *it_cls = nullptr;
                EMC_FINDCLASS(it_cls, it.idName);
                if (_.showMenus) {
                    it_cls->draw_begin(); // <-- BEGIN A DRAW **************************************************
                    for (auto &it1 : it_cls->layout.items) {
                        ClassBase *it1_cls = nullptr;
                        EMC_FINDCLASS(it1_cls, it1.idName);
                        MenuItem *menuItem = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                        _.menuItems.push_back(menuItem);
                    }
                    it_cls->draw_end(); // <-- END A DRAW **************************************************
                }
                else {
                    auto &it1 = it;
                    ClassBase *it1_cls = it_cls;
                    _.collapsedMenu = new MenuItem(this, Rect({ 0, 0 }, { 0, 0 }), this, it1.itemType, it1_cls);
                }
            }
            else {
                EMC_ERROR_WRONGTYPE();
            }
        }
        _.cls->draw_end(); // <-- END A DRAW **************************************************
    }
    

    The real checking function that shows warning about memory leak is like this:

    // The warning is placed in one single function
    void internal_imguiDebug_classStack() {
        static int g_frame = 0;
        g_frame++;
        if (g_frame == 50) { // Performance reason
            g_frame = 0;
    
            for (auto &cls : g_classStack) { // This is placed here because no need to expand a TreeNode to evaluate it.
                auto items = cls->layout.items;
                if (items.size() != 0) {
                    static bool shown = false;
                    if (!shown) {
                        shown = true;
                        EMC_ERROR_NOBREAK((std::string("Class Stack > ") + cls->emc_idName + ": A layout must always be cleared after the items are created. You didn't call " + FUNCTION_TO_STRING(ClassBase::draw_end) + ".").c_str());
                    }
                }
            }
        }
        ...
    }
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.