简体   繁体   中英

Returning pointers to heap objects without smart pointers

I have an abstract class, IPacket.

/*
 * Represents an abstract network packet.
 */
class IPacket
{

public:

    virtual void read(...) = 0;

    virtual void write(...) = 0;

    virtual int getID() const = 0;
};

And I'd like to (and have been) returning a pointer to like so:

class PacketMedium
{
    /*!
     * Returns a pointer to the next pending packet or NULL if there are no packets pending.
     * \note The object must deleted when you are finished with it.
     */
    IPacket* receivePacket();
};

Now obviously, this is bad practice; requiring the caller to delete a pointer that wasn't even allocated by itself. The convention I believe, is to use smart pointers, ie

class PacketMedium
{
public:

    std::unique_ptr<IPacket*> receivePacket();
};

But as this is library code, smart pointers are a nono, notwithstanding the fact I'd prefer to avoid them regardless.

What would my best option be?

Thanks for any help :)

EDIT: this has been asked before and very good answers were given, although they all suggest smart pointers, or just not allocating on the heap. Given that IPacket is abstract, stack allocation wouldn't work.

One idea is to return a reference:

class PacketMedium {
public:
   IPacket &receivePacket();
private:
   IPacketImpl1 impl1;
   IPacketImpl2 impl2;
 };

The receivePacket should be implemented in the following way:

IPacket &receivePacket() {
  int data = receiveint();
  if (data==0) { // impl1
      float data = receivefloat();
      impl1.data = data;
      return impl1;
  } else { // impl2
      std::string data = receivestr();
      impl2.str = data;
      return impl2;
  }
}

Note that there will be some ground rules when using the reference:

  1. Calling receivePacket() twice is dangerous. The 2nd call to it might erase existing data.
  2. The data you receive should be immediately used. Storing IPackets for longer time is dangerous.

To fix these problems, you could implement new function to IPacket interface:

virtual IPacket *clone() const=0;

You don't even need to return a reference. You can just wrap the public/relevant IPacket interface behind a class which also has advance operations and takes care of the deletion of incoming packets as they are consumed (ie your type calls receivePacket() ).

If binary compatibility is your main concern, you can just write your own simple unique_ptr.

Another solution is "handles". Instead of unique_ptr<IPacket> reveivePacket() you could wrap the unique_ptr to a type like this:

struct IPacketHandle { int id; };
IPacketHandle receivePacket();

But then where is packets stored? It would be inside std::vector<IPacket*> vec , but it's internal to your code. Then do vec.push_back(new IPacketImpl1); handle.id = vec.size()-1; vec.push_back(new IPacketImpl1); handle.id = vec.size()-1; . Note that deleting items from the vector might be more complicated/should replace the object with NULL pointer.

But wait, now you lost IPacket interface's read/write functions. Need to add them again:

void read_packet(IPacketHandle h, ...) { vec[h.id]->read(...); }
void write_packet(IPacketHandle h, ...) { vec[h.id]->write(...); }

(I've used this solution successfully in a library, and it gives nice functional programming interface similar to haskell's standard library, where there is no memory management for library users at all) (also the location of std::vector needs to be consiudered carefully, so that global variables dont need to be used)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM