Search code examples
c++shared-ptr

boost::shared_ptr in a modified factory method


I have a custom IPC system, based on network (TCP-IP).

Consider the code (and the explanation bellow):

#include "boost/shared_ptr.hpp"
#include <string>

using namespace std;

class TCommand {
public:
  typedef boost::shared_ptr<TCommand> Ptr;

  TCommand() {
      cout << "    Creating TCommand..." << endl;
  }

  virtual ~TCommand() {
      cout << "    Destroying TCommand..." << endl;
  }

  static TCommand * factory(int classID);

  virtual void parse(const char *data, int dataSize) = 0;
  virtual void print() = 0;
  virtual std::string getType() = 0;

};


class TPingCommand : public TCommand {
public:
  static const int classID = 1;
  int value;

  TPingCommand() : TCommand() {
      cout << "    Creating TPingCommand..." << endl;
  }

  virtual ~TPingCommand() {
      cout << "    Destroying TPingCommand..." << endl;
  }

  virtual void parse(const char *data, int dataSize) {
    if (dataSize < 4) throw 1;

    this->value = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
  }

  virtual void print() {
      cout << "  TPingCommand:" << endl;
      cout << "    value = " << dec << this->value << " (0x" << hex << this->value << ")" << endl;
  }

  virtual std::string getType() {
      return "TPingCommand";
  }
};

class TOtherCommand : public TCommand {
public:
  static const int classID = 2;
  int value;
  char value2;
  short int value3;

  TOtherCommand() : TCommand() {
      cout << "    Creating TOtherCommand..." << endl;
  }

  virtual ~TOtherCommand() {
      cout << "    Destroying TOtherCommand..." << endl;
  }

  virtual void parse(const char *data, int dataSize) {
    if (dataSize < 7) throw 1;

    this->value = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
    this->value2 = data[4];
    this->value3 = data[5] << 8 | data[6];
  }

  virtual void print() {
      cout << "  TOtherCommand:" << endl;
      cout << "    value  = " << dec << this->value << " (0x" << hex << this->value << ")" << endl;
      cout << "    value2 = " << dec << this->value2 << " (0x" << hex << (int)this->value2 << ")" << endl;
      cout << "    value3 = " << dec << this->value3 << " (0x" << hex << this->value3 << ")" << endl;
  }

  virtual std::string getType() {
      return "TOtherCommand";
  }
};


TCommand * TCommand::factory(int classID) {
    cout << "  Factory for classID = " << dec << classID << " (0x" << hex << classID << ")" << endl;
    switch (classID) {
    case TPingCommand::classID: return new TPingCommand(); break;
    case TOtherCommand::classID: return new TOtherCommand(); break;
    default: throw 1;
    }
  }


TCommand::Ptr receiveFromNetwork(int test, TCommand::Ptr knownCommand)
{
    // Receive command header from network.
    // int classID is the command class internal ID.
    // int dataSize is the command's body size in bytes.
    // For instance:
    //   int classId = 2;
    //   int datasize = 7;

    int classId = 1;
    int dataSize = 4;
    char data[10];

    if (test == 0) {
        cout << "  Using test data 0..." << endl;
        classId = 1;
        dataSize = 4;
        data[0] = 0x01; data[1] = 0x02; data[2] = 0x03; data[3] = 0x04;
    } else if (test == 1) {
        cout << "  Using test data 1..." << endl;
        classId = 2;
        dataSize = 7;
        data[0] = 0x11; data[1] = 0x12; data[2] = 0x13; data[3] = 0x14; data[4] = 0x41; data[5] = 0x16; data[6] = 0x17;
    }

    TCommand::Ptr cmd;
    if (knownCommand == 0) {
        cout << "  No command provided." << endl;
        cmd.reset(TCommand::factory(classId));
        cout << "  Command created from factory: " << cmd->getType() << endl;
    } else {
        cmd = knownCommand;
        cout << "  Command provided: " << cmd->getType() << endl;
    }

    cout << "  Parsing data..." << endl;
    cmd->parse(data, dataSize);

    // The command was identified as TOtherCommand (classID = 2).
    // The factory returned a TOtherCommand instance.
    // The TOtherCommand's parse method will check the dataSize is suitable (7 bytes are necessary).
    // The parse method will unserialize data to their fields.
    // This way, the fields would be:
    //    data = 0x11121314;
    //    data2 = 0x42; // 'A' as char.
    //    data3 = 0x1213;

  return cmd;
}

void caller() {
    // Case 1 (ok):
    // I know I'm going to receive a TPingCommand.
    cout << "Test case 1:" << endl;
    TCommand::Ptr known(new TPingCommand());
    TCommand::Ptr cmd1 = receiveFromNetwork(0, known);
    cmd1->print();

    // Case 2 (problems):
    cout << "Test case 2:" << endl;
    TCommand::Ptr dummy;
    TCommand::Ptr cmd2 = receiveFromNetwork(1, dummy);
    cmd2->print();

    cout << "Teardown..." << endl;
}

int main() {
    caller();
}

The receiveFromNetwork is sort of a modified factory method which is used to receive a command from the network, however, in a few cases, I know by priori which type of command I'm going to receive, so I create the instance of it and pass to the function (as knownCommand). The command class is derived from the TCommand class. The knownCommand is returned by the function (it wouldn't be necessary, since you passed that as parameter, but it is useful for other cases).

All other cases, the first few bytes received from the network describe the command classID and I use it to create the suitable TCommand instance inside this function. The command is then parsed from network data and returned in the end of the function. The knownCommand is just a dummy TCommand instance.

When I pass the knownCommand, it works well. When I pass a dummy command as parameter, it crashes (double free as far as I know).

I thought about using a TCommand reference for knownCommand, however, I can't do that, because I would have to return a shared pointer and it would cause the same raw pointer to be managed by two different shared pointer instances (one from the caller method and the other inside the receiveFromNetwork method).

Anyone have a good idea on how to solve that?

Here is a partial valgrind output of the problematic scenario:

==31859== Thread 2:
==31859== Invalid read of size 4
==31859==    at 0x805D7B0: boost::detail::sp_counted_impl_p<TVideoGetSourceSizeCommand>::dispose() (checked_delete.hpp:34)
==31859==    by 0x407FF42: Server::receiveFromNetwork() (sp_counted_base_gcc_x86.hpp:145)
==31859==    by 0x40800AF: Server::serverThread(void*) (Server.cpp:107)
==31859==    by 0x434496D: start_thread (pthread_create.c:300)
==31859==    by 0x42B398D: clone (clone.S:130)

Thanks very much.


Solution

  • Found the problem.

    In the calling part of the code, there was a shared_ptr being "duplicated" the wrong way. The resulting mistake was something like this:

    boost::shared_ptr<DerivedType> ptr1(new DerivedType(...));
    boost::shared_ptr<BaseType> ptr2(ptr1.get());
    

    Of course it was going to cause a duplicated FREE and a program crash.

    The code wasn't that dumb, but in the end that was the simplified error scenario. Even though, it was a trivial mistake.

    Thanks for all the help guys.