【问题标题】:Classes, constructor and pointer class members类、构造函数和指针类成员
【发布时间】:2010-04-02 05:01:14
【问题描述】:

我对对象引用有点困惑。请检查以下示例:

class ListHandler {
public:
   ListHandler(vector<int> &list);
private:
   vector<int> list;
}

ListHandler::ListHandler(vector<int> &list) {
   this->list = list;
}

因为内部

vector<int> list;

定义,在这里我会浪费内存对吗?所以正确的应该是:

class ListHandler {
public:
   ListHandler(vector<int>* list);
private:
   vector<int>* list;
}

ListHandler::ListHandler(vector<int>* list) {
   this->list = list;
}

ListHandler::~ListHandler() {
   delete list;
}

基本上我想要的只是创建一个向量并传递给 ListHandler。这个向量不会在 ListHandler 本身以外的任何地方使用,所以我希望 ListHandler 能够完成所有其他事情和清理等工作。

【问题讨论】:

  • 这是干什么用的?更多的上下文会给你更好的答案。

标签: c++ class pointers


【解决方案1】:

这取决于您是否要共享底层向量。一般来说,我认为尽可能避免共享是一个好习惯,因为它消除了对象所有权的问题。不分享:

类 ListHandler { 上市: ListHandler(const std::vector& list) : _list(list) {} ~ListHandler(){} 私人的: std::vector _list; };

请注意,与您的示例不同,我将其设为const,因为不会修改原始内容。但是,如果我们想保留并共享同一个底层对象,那么我们可以使用这样的东西:

类 ListHandler { 上市: ListHandler(std::vector& list) : _list(&list) {} ~ListHandler(){} 私人的: std::vector* _list; };

请注意,在这种情况下,我选择让调用者作为对象的所有者(因此调用者有责任确保列表在列表处理程序对象的生命周期内存在,并且列表稍后被释放)。您接管所有权的示例也是一种可能性:

类 ListHandler { 上市: ListHandler(std::vector* list) : _list(list) {} ListHandler(const ListHandler& o) : _list(new std::vector(o._list)) {} ~ListHandler(){ 删除 _list; _list=0; } ListHandler& swap(ListHandler& o){ std::swap(_list,o._list);返回*这个; } ListHandler& operator=(const ListHandler& o){ ListHandler cpy(o);返回交换(cpy); } 私人的: std::vector* _list; };

虽然上述情况当然是可能的,但我个人不喜欢它......我发现对于一个不仅仅是智能指针类的对象获取指向另一个对象的指针的所有权是令人困惑的。如果我这样做,我会通过将 std::vector 包装在智能指针容器中来使其更明确,如下所示:

类 ListHandler { 上市: ListHandler(const boost::shared_ptr >& list) : _list(list) {} ~ListHandler(){} 私人的: boost::shared_ptr<:vector> > _list; };

我发现上述内容在传达所有权方面更加清晰。但是,所有这些传递列表的不同方式都是可以接受的……只要确保用户知道谁将拥有什么。

【讨论】:

  • 注意:在第三个示例(对象所有者)中,您没有重新定义复制和分配,因此您实际上有未定义的行为等待发生。此外,在没有明确说明的情况下取得所有权也不是一个好主意,如果您取得指针的所有权,则需要在您的界面中传递一个类似unique_ptr 的智能指针,以便调用者不会意外调用它并引用例如,堆栈上的一个对象。
  • @Mathhieu,谢谢。接得好。我会解决的。我自己不喜欢这种解决方案的原因之一。
【解决方案2】:

第一个例子不一定是浪费内存,它只是在“this->list = list;”处复制整个向量行(这可能是您想要的,取决于上下文)。那是因为 vector 上的 operator= 方法在此时被调用,对于 vector 制作了自身及其所有内容的完整副本。

第二个例子绝对不是复制向量,只是分配一个内存地址。虽然 ListHandler 构造函数的调用者更好地意识到 ListHandler 正在接管指针的控制,因为它最终会释放内存。

【讨论】:

  • 如果向量 是通过“new”创建的,然后该向量的所有权将转移给 ListHandler 稍后将其销毁,并且您不想复制向量在这个过程中,那么第二个选项会为你做到这一点。
  • 但是除了尝试通过“更少”vector 对象来节省内存之外,您还应该有转移所有权的理由。
【解决方案3】:

这取决于调用者是否希望继续使用他们的列表(在这种情况下你最好不要删除它,并且需要担心它会在你最不期望的时候改变),以及调用者是否会销毁它(在这种情况下如果您最好不要保留指向它的指针)。

如果你的类的文档是调用者用 new 分配一个列表,然后在调用你的构造函数时将所有权移交给你的类,那么保持指针很好(但使用 auto_ptr 所以你不必写“自己删除列表”,担心异常安全)。

【讨论】:

  • 是的,我要做的就是将所有权转移给 ListHandler。第二个适合这个目的吗?
  • 是的,你的第二个例子很好。
【解决方案4】:

这完全取决于您想要什么,以及您可以确保哪些政策。您的第一个示例没有任何“错误”(尽管我会通过选择不同的名称来避免明确使用this-&gt;)。它制作了向量的副本,这可能是正确的做法。这可能是最安全的做法。

但您似乎想重用同一个向量。如果保证列表比任何 ListHandler 寿命更长,则可以使用引用而不是指针。诀窍是引用成员变量必须在构造函数中的初始化列表中进行初始化,如下所示:

class ListHandler
{
public:
    ListHandler(const vector<int> &list)
    : list_m(list)
    {
    }
private:
    vector<int>& list_m;
};

初始化列表是冒号之后,正文之前的位。

但是,这不等同于您的第二个示例,它使用指针并在其析构函数中调用 delete。这是第三种方式,其中 ListHandler 承担列表的所有权。但是代码有危险,因为通过调用delete,它假定列表是用new 分配的。澄清此政策的一种方法是使用标识所有权变更的命名约定(例如“采用”前缀):

ListHandler::ListHandler(vector<int> *adoptList)
: list_m(adoptList)
{
}

(这和你的一样,除了名称更改和使用初始化列表。)

所以现在我们看到了三个选择:

  1. 复制列表。
  2. 保留对他人拥有的列表的引用。
  3. 承担某人使用new 创建的列表的所有权。

还有更多选择,比如做引用计数的智能指针。

【讨论】:

    【解决方案5】:

    没有单一的“正确方法”。但是,您的第二个示例将是非常糟糕的样式,因为 ListHandler 在构造时获取了向量的所有权。如果可能的话,每个new 都应该与其delete 紧密配对——说真的,这是一个非常高的优先级。

    如果vector 的寿命与ListHandler 的寿命一样长,那么它还不如 ListHandler 中存在。如果你把它放在堆上,它不会占用更少的空间。事实上,堆增加了一些开销。所以这根本不是new 的工作。

    你也可以考虑

    ListHandler::ListHandler(vector<int> &list) {
       this->list.swap( list );
    }
    

    如果您希望清除初始化列表并避免复制向量内容的时间和内存开销。

    【讨论】:

    • Swap 交换内容,这不是我想做的。我只是不想创建 2 个矢量对象。我猜在我的定义中:vector list;在课堂上创建一个。在构造函数中,我将另一个向量传递给它,但它已经创建了。所以我传递了参考,但这就像我正在创建一个向量,然后我的变量开始指向另一个不好的向量。
    • 我根本不会认为第二个例子很糟糕。紧密配对新/删除并不是一个“非常高的优先级”。工厂函数将它们完全分开。第二个例子是所有权转让的一个很好的例子。
    • @pocoa:swap 只是将源列表的内容移动到目标。当你传递一个源向量时,它可能在堆栈上。 ListHandler 中的向量必须在其他地方。因此,您必须有两个矢量对象。它们非常轻巧;您只能通过尝试使用new vector 最小化程序来使程序变得更大更慢。
    • @Stephen:不,工厂函数只是代替了new。对工厂的调用与delete 配对,就像new 一样。过度的所有权转移会破坏 RAII,这是 C++ 的内存管理理念。
    • @Potatocorn:我认为最好使用“assign”,而不是“swap”。
    猜你喜欢
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多