【问题标题】:Returning pointers to heap objects without smart pointers返回指向没有智能指针的堆对象的指针
【发布时间】:2013-07-06 09:25:54
【问题描述】:

我有一个抽象类,IPacket。

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

public:

    virtual void read(...) = 0;

    virtual void write(...) = 0;

    virtual int getID() const = 0;
};

我想(并且一直)返回一个指向这样的指针:

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();
};

现在显然,这是不好的做法;要求调用者删除一个甚至不是自己分配的指针。我相信的约定是使用智能指针,即

class PacketMedium
{
public:

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

但由于这是库代码,智能指针是一个 nono,尽管事实上我宁愿避免它们。

我最好的选择是什么?

感谢您的帮助:)

编辑:之前有人问过这个问题并给出了很好的答案,尽管它们都建议使用智能指针,或者只是不在堆上分配。鉴于IPacket 是抽象的,堆栈分配将不起作用。

【问题讨论】:

  • 我不明白;要么你想确保调用者deletes 是对象,要么你可以使用智能指针为你管理它,但你似乎也不想要。那你想要什么?
  • 也许他想在没有任何类型指针的情况下返回对象。如今,编译器擅长复制省略。
  • 我相信我必须使用动态分配IPacket,因为派生类有不同的大小,所以在堆栈中使用IPacket 的派生类是不可能的。智能指针似乎是我唯一的选择。我正在寻找某种方法来重组我的代码,以便我可以创建 IPacket 对象并以一种明显的方式删除它们。
  • Apart from the fact that many libraries were written before the advent of standard smart pointers, the biggest reason is probably the lack of a standard C++ ABI. stackoverflow.com/questions/10334511/…
  • @Dylan 这是一个糟糕的理由。好的库在其接口中使用 C++ 类型,不这样做的库不是一个很好的库。并且缺乏标准化的二进制接口并没有你想象的那么严重,因为你只需要为不同的架构重新编译库,对于 C 和 C++ 来说,这无论如何都是很常见的。摘要:使用智能指针,这是正确的解决方案。

标签: c++ pointers memory


【解决方案1】:

一个想法是返回一个引用:

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

receivePacket的实现方式如下:

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;
  }
}

请注意,使用参考时会有一些基本规则:

  1. 两次调用receivePacket() 是危险的。第二次调用它可能会删除现有数据。
  2. 您收到的数据应立即使用。长时间存储 IPackets 很危险。

要解决这些问题,您可以为IPacket接口实现新功能:

virtual IPacket *clone() const=0;

【讨论】:

  • 这实际上是一个非常有帮助的答案,尽管我打算稍等片刻,看看是否有更好的选择;限制的地方(即存储更长时间)有点拖延。
  • '稍等片刻,看看是否有更好的选择' 对于你给予的限制,不要真的这么想。我也没有看到为什么在库 API 中使用智能指针应该是不好的原因。
【解决方案2】:

您甚至不需要返回引用。您可以将公共/相关IPacket 接口包装在一个类后面,该类也具有高级操作并在传入数据包被使用时负责删除它们(即您的类型调用receivePacket())。

如果二进制兼容性是您主要关心的问题,您可以编写自己的简单 unique_ptr。

【讨论】:

    【解决方案3】:

    另一种解决方案是“句柄”。而不是unique_ptr&lt;IPacket&gt; reveivePacket(),您可以将 unique_ptr 包装成这样的类型:

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

    但是数据包存储在哪里?它会在std::vector&lt;IPacket*&gt; vec 内部,但它在您的代码内部。然后做vec.push_back(new IPacketImpl1); handle.id = vec.size()-1;。请注意,从向量中删除项目可能更复杂/应该用 NULL 指针替换对象。

    但是等等,现在你失去了 IPacket 接口的读/写功能。需要重新添加:

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

    (我已经在一个库中成功使用了这个解决方案,它提供了类似于 haskell 标准库的很好的函数式编程接口,其中根本没有库用户的内存管理) (也需要仔细考虑std::vector的位置,这样就不需要使用全局变量了)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-19
      • 2014-05-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多