views:

84

answers:

3

I've messed up something. Here is the code:

#include <iostream>

class connection_c {
  private:
    std::string data_;
    void (*saveCallBack_)();
  public:
    connection_c(std::string &data) : data_(data) { std::cout << "ctor: " << __FUNCTION__ << ":" << data_ << std::endl;}
    void registerCallBack(void(*cb)()) { saveCallBack_ = cb; }
};

class inst_c {
  private:
    static int id;
    connection_c conn;
    static void cb() { std::cout << __FUNCTION__ << " id = " <<  id << std::endl; } 
  public:
    inst_c(connection_c &c, int a) : conn(c),  id(a)  { 
      std::cout << "ctor: " << __FUNCTION__ << " " << id << std::endl;
      conn.registerCallBack(&cb); 
    }
};

class group_inst_c {
  private:
    connection_c conn;
    inst_c i,j,k;
  public:
    group_inst_c(std::string data) : conn(data), i(conn,1), j(conn,2), k(conn,3) {} 
};


int main() {
  group_inst_c gi("asdf");
  return 0;
}

What I want to achieve ;)

  • create a group of instances (group_inst_c)
  • it should initialize single connection for the group (connection_c)
  • each instance (inst_c) should use this connection (it will be serialized)
  • .. in addition each instance should register separate callback

For sure I've messed up with cloning, but I guess probably not only. Can someone help me solve this puzzle? thx.

A: 

If I understood that correctly (you want to call several callbacks from a single connection [object]), you need a list in connection_c to register the callbacks (just like delegates in C# if you know them).
If an event occurs to this connection, it has to know where to report. So you have to iterate through the callbacks somehow (call them one by one; you cannot call them all at once). The easiest, straightforward way is to use an STL list or maybe boost offers something appropriate.
Take a look at this: A C++ delegate class. In the main function, there's a vector defined that takes multiple callbacks. You could use this pattern in you connection_c class to add and not set a callback.

DyP
+1  A: 

Your code creates a copy of your connection object for each instance. The original connection object is then only accessible by your group_inst_c. Is this what you want? If not, you need to change:

class inst_c {
  private:
    static int id;
    connection_c& conn; // <-- Needs to be a reference.

in addition each instance should register separate callback

I'm not sure what you mean here. Are the callbacks supposed to be member functions? Then you need to use a "pointer to member function" (the ::*, .*, and ->* operators). If the callbacks are supposed to be regular functions, you should be okay with your current code. You'll just need to add this to class connection_c:

void doCallback(void) { (*saveCallBack_)(); }
Mike DeSimone
A: 

Try to keep it simple at first. There's always an opportunity to grow/improve the design later on. Below is some example code and here are a couple of things I was thinking about while building it:

1) As mentioned, keep it simple. For example, maybe the group concept can be a vector (i.e. inst_group_t) to start. You can always grow the design later as you learn more about it.

2) Try to reduce class dependencies. For example, maybe I do not need to have the connection as a member variable. I can pass it in when its needed (i.e. execute()). Maybe the callback doesn't need to be registered (i.e. execute()), since its 1 connection_c to many inst_c instances registering a callback for each inst_c would mean connection would have some container. Keep it simple :)

3) Try to use const and reference as much as possible (i.e. connection_c constructor). Less copy constructors/temp objects will be created.

#include <iostream>

class connection_c {
  private:
    std::string data_;
  public:
    connection_c(const std::string &data) : data_(data) { 
        std::cout << "ctor: " << __FUNCTION__ << ":" << data_ << std::endl;
    }
};

class inst_c {
  private:
    int id;
  public:
    inst_c(int a) : id(a)  { 
      std::cout << "ctor: " << __FUNCTION__ << " " << id << std::endl;
    }

    typedef void (*execute_callback_t)(int i);
    void execute(connection_c& connection, execute_callback_t callback) {
        callback(id);
    }
};

void mycallback(int id) {
    std::cout << "Instance number " << id << " executed" << std::endl;
}

int main() {

    typedef std::vector<inst_c*> inst_group_t;
    inst_group_t group;

    std::string data;
    connection_c connection(data);

    for (int i = 0; i < 10; ++i) 
        group.push_back(new inst_c(i) );

    for (int i = 0; i < 10; ++i) 
        group[i]->execute(connection, mycallback);

    for (int i = 0; i < 10; ++i) 
        delete group[i];

    return 0;
}
skimobear
you could add one thing: don't use callbacks, they're evil (when used as pointers). Use inheritance or templates instead. I don't see any reason for callbacks here. Besides, the vector version isn't very OOP imho. Yes it's simple, but not _intuitive_. You have to care about the vector all the time, and iterate for each use of execute. A type that wraps this would be simple and adequate imho.
DyP
@DyP - Could definitely wrap the vector. Especially since it contains pointers. A class with an RAII pattern would be nice to help with the cleanup. Not sure what the callback is for in this case but callbacks definitely have a place in this world.
skimobear