0

I am trying to do something I thought was simple: calling functions that call functions that call functions (from inherited classes) using a chain of pointers. Is there a better way of accomplishing this? Also, in the spirit of C++11, how would I incorporate smart pointers in this example? This example crashes the application:
Here is the sample code, apologies if it looks a little silly:

Actual output (Crashes!):

    almost there...

Desired output:

    almost there...
    hello from function1

f1.h:

    #ifndef FUNCTION_1_H
    #define FUNCTION_1_H
    //f1.h (header file)

    #include <iostream>

    struct f1{

    int _a;
    f1() {}
    void function1();

    };

    #endif

f2.h:

    #ifndef FUNCTION_2_H
    #define FUNCTION_2_H
    //f2.h (header file) 


    #include "f1.h"

    struct f2 : public f1{

    int _b;
    f1* f1_ptr;
    f2() :f1(){}
    void function2();

    };

    #endif

f3.h:

    #ifndef FUNCTION_3_H
    #define FUNCTION_3_H


    #include "f2.h"

    struct f3 :public f2{

    int _c;
    f2* f2_ptr;
    f3() : f2(){}
    void function3();

    };

    #endif

CPPs:

f3.cpp:

    #include "f3.h"


    void f3::function3(){

    //do stuff...
    //e.g. calculate an int Var3
    f2_ptr->function2(/*pass Var3 as argument*/);
    }

f2.cpp:

    #include "f2.h"


    void f2::function2(/*receive Var3*/){

    //do stuff with Var3
    //e.g. maybe use Var3 to calculate an int Var2

    std::cout << "almost there..." << std::endl;
    f1_ptr->function1(/*pass Var2 as argument*/);
    }

f1.cpp:

    #include "f1.h"


    void f1::function1(/*receive Var2*/){

    //take Var2 and use elsewhere
    //or continue linking to other functions
    std::cout << "hello from function1" << std::endl;
    }

main.cpp:

    int main(){

    f3* ptr3 = new f3;
    ptr3->function3();
    //delete ptr3;
    std::cin.get();
    return 0;
    }
lewisjb
  • 678
  • 10
  • 26
Chewco
  • 249
  • 2
  • 10

1 Answers1

1

The problem is that in the upper classes, the pointers f2* and f1* are not initialized, so when you do f2_ptr->function2(), you are trying to access a member function via an uninitialized pointer, which leads to UB (undefined behaviour). Your code is doing basically this:

#include <iostream>

using namespace std;

struct Base
{
    void f(){cout << "In f" << endl;}
};

struct Derived
{
    Base* ptr;
};

int main()
{
    Derived* foo;
    foo->ptr->f(); //cannot use foo->ptr, it is not initialized
}

So you have to make sure that in the constructors of f3 you initialize the f2_ptr and so on. About smart pointers, you can use std::unique_ptr or std::shared_ptr, the syntax is std::unique_ptr<Foo> pointer( new Foo ) (and similarly for std::shared). It is highly recommended to use them, for example, you HAVE TO initialize them (cannot arrived at this problem if you'd have used smart pointers)

Here is a hint for how to write f3.cpp:

#include "f3.h"

// define the constructor here (and just declare it in the header `f3.h`)
f3::f3() : f2()
{
    auto f2_ptr = std::make_shared<f2>(); 
    // and all our nightmares are over, f2_ptr is now a valid pointer
    // which will automatically release the allocated memory
    // when the reference count is zero
}

void f3::function3()
{
    //do stuff...
    //e.g. calculate an int Var3
    f2_ptr->function2(/*pass Var3 as argument*/);
}

And since I started this, here is a full C++11 example (it uses in-class initialization) with chaining and that uses smart pointers, which works and is basically equivalent to yours:

#include <iostream>
#include <memory>

using namespace std;

struct Base
{
    void f_base()
    {
        cout << "In f_base" << endl;
    }
};

struct Derived
{
    void f_derived()
    {
        cout << "In f_derived" << endl;
    }
    std::shared_ptr<Base> ptrBase = make_shared<Base>();
};

struct DerivedDerived
{
    std::shared_ptr<Derived> ptrDerived = make_shared<Derived>();
};

int main()
{
    DerivedDerived *foo = new DerivedDerived;
    foo->ptrDerived->ptrBase->f_base(); // OK now
}

PS: this may help you understand what's going on When does invoking a member function on a null instance result in undefined behavior?

Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • Thanks for the suggestion vsoftco! And for the intro to smart pointers. However, the motivation for the example I gave was to call function2 first, then within function2 have a condition to call function1 or call some other functionX (maybe function1 is never called), but your solution seems to create a pointer to a pointer, jumping straight to function1. Any thoughts? – Chewco Aug 09 '14 at 01:20
  • Even if it seem to work calling just `foo->ptrDerived->ptrBase` (just one level of indirection, and indeed this "seems" to work), it is still undefined behaviour. So every time you do something like `pointer->call_something`, you absolutely must make sure that `pointer` is valid (in other words, it is not `nullptr`). Doesn't your code work if you use smart pointers or initialize the pointers? – vsoftco Aug 09 '14 at 01:24
  • No, using my code - the application still crashes when I include the shared_ptr in the f3() constructor. – Chewco Aug 09 '14 at 03:11
  • @Chewco I don't understand why. Isn't your code exactly the same as the code I posted at the end of my answer (immediately above these comments)? – vsoftco Aug 09 '14 at 03:25
  • I copy/pasted it exactly as is. I have been having major problems with VC++ 2013 lately. – Chewco Aug 09 '14 at 03:40
  • Oh wait: the "hint to how to write f3.cpp" still crashes. The code at the end of your answer compiles, but I am not sure if that solution can take variables calculated in f_derived and use in f_base. – Chewco Aug 09 '14 at 03:46
  • Please put all the code in one single file, like `main.cpp`, and update your post. It is much easier to see exactly what's happening. – vsoftco Aug 09 '14 at 19:11
  • I have found a solution to the problem, but I would like your thoughts as to why it behaves this way: When I put the `auto f2_ptr = std::make_shared()` in the `f3` constructor, it crashes. However, when I initialize that pointer in `f3::function3()`, it works perfectly. Why does it do this? – Chewco Aug 10 '14 at 23:26