【问题标题】:Member object of C++ class: Where should it be destroyed?C++ 类的成员对象:应该在哪里销毁?
【发布时间】:2017-02-18 09:53:00
【问题描述】:

我对以下案例有些困惑。我有以下 C++ 类,代表一个 RGB 像素:

  class RgbColorVector
  {
       cv::Mat pM;

  public:
    RgbColorVector(unsigned int r,unsigned int g,unsigned int b)
    {
         pM = cv::Mat(3,1,CV_8UC1);
         pM.at<unsigned char>(0,0) = r;
         pM.at<unsigned char>(1,0) = g;
         pM.at<unsigned char>(2,0) = b;
}


unsigned int getComponent(int c)
{
    return (unsigned int)pM.at<unsigned char>(c,0);
}

这里我使用 OpenCV Mat 对象来保存一个 RGB 值。通常在这种情况下,我会使用 cv::Mat 指针并在构造函数中使用 new 运算符分配堆内存,然后在析构函数中使用 delete 释放它。在上面的例子中,我显式地调用了 cv::Mat 的构造函数。我的预期是,当 RgbColorVector 的构造函数退出时, cv::Mat 构造函数中分配的内存将被破坏。但显然情况并非如此:

 void func()
 {
     RgbColorVector rgbVec(105,42,45);
     int g = rgbVec.getComponent(1);
     std::cout<<g<<std::endl;
 }


int main(int argc, const char * argv[]) {
    func();
    return 0;
}

在这里,我得到输出 42。我知道“rgbVec”是一个本地(自动)变量,当“func”退出时,它的内容(类成员)将被销毁。但此外,我预计 getComponent() 调用将返回一个随机值,因为在 cv::Mat 构造函数调用中分配的任何内存都将在“rgbVec”的构造函数返回后被销毁。我在这里有点困惑,在这种情况下内存分配机制究竟是如何工作的? cv::Mat 对象不是在 RgbColorVector() 构造函数的栈上分配的吗?

【问题讨论】:

  • 我不知道你会期待什么样的行为。您正在使用 cv::Mat 的复制构造函数 pM = cv::Mat(3,1,CV_8UC1);所以构造的对象在你的类成员变量中(如果类对象被销毁,它将被销毁),它位于 func() 的末尾。此外,如果您真的要访问已释放的内存,则不会返回“随机”值,而是返回内存中的值。如果内存仍然属于您的程序并且没有被覆盖,如果您不小心释放了内存,您甚至可能会观察到“正确”的值。
  • 使用 Mat 存储单个像素值非常糟糕。你最好使用 Vec3b,或者只使用 3 个 uchars

标签: c++ opencv memory-management constructor


【解决方案1】:
class RgbColorVector
{
    cv::Mat pM; // <- this object is valid as long
                //    as the parent is (value semantics)

public:
    RgbColorVector(unsigned int r,unsigned int g,unsigned int b)
        // <- pM gets implicitly allocated here
    {
        pM = cv::Mat(3,1,CV_8UC1); // <- this creates a temporary, and
                                   //    assign its value to pM

        // <- the temporary gets implicitly deallocated here.
        //    pM isn't affected.

        pM.at<unsigned char>(0,0) = r;
        pM.at<unsigned char>(1,0) = g;
        pM.at<unsigned char>(2,0) = b;
    }
};

初始化成员的正确方法是这样的:

    RgbColorVector(unsigned int r,unsigned int g,unsigned int b)
        : pM(3,1,CV_8UC1)
    {

这称为成员初始化列表

【讨论】:

  • 当使用 cv::Mat 调用创建临时对象时,它会在堆中分配内存来保存颜色值。临时 cv::Mat 的所有类成员都与数据指针一起复制到 pM 类成员中,所以堆上的这个像素数据仍然有效,是吗?
  • cv::Mat 可能会或可能不会在内部使用堆,这取决于该类,并且对用户没有影响。 C++ 使用值语义,即类的行为应该像 int 那样,如果你复制一个值,不管在原始值之后发生什么,副本不受影响。
  • @BattleBeast 没错。它的工作方式类似于共享指针。
  • @sp2danny 在这种情况下并不完全正确,了解cv::Mat 的工作原理确实很有意义(正如 SO 上的许多问题所示)。例如。 assignment: "矩阵赋值是一个 O(1) 运算。这意味着不会复制数据,但会共享数据,并且引用计数器(如果有)会递增。"
【解决方案2】:

您正在调用 cv::Mat 复制 ctor,因此新创建的临时 cv::Mat 将移动到已构建的 pM。因此,pM 在父对象的作用域结束之前保持构造和有效。 pM 永远不会被破坏,pM 持有的任何数据也不会被破坏。

然而,OpenCV 增加了另一个维度的间接性。 cv::Mat 通过指针存储数据以防止在不需要时重复,这是一种浅拷贝形式。它本身巧妙地管理堆上的数据,并在不再被任何其他cv::Mat 引用时删除数据。因此,即使是堆栈上的cv::Mat 对象也包含堆上的一些数据,以及堆栈上的其他详细信息,您的pM 就是这种情况。不相关,但由于您对cv::Mat 的范围及其数据感到困惑,我添加了一些细节。

编辑:临时变量、移动和默认 ctor 感谢sp2danny

【讨论】:

  • 不是复制构造函数被调用,首先是默认构造函数被隐式调用,然后是一个临时的显式构造被调用,然后是移动赋值
  • ...这可以通过在 pM en.cppreference.com/w/cpp/language/initializer_list 的构造函数中使用初始化列表来纠正
猜你喜欢
  • 2013-07-13
  • 2014-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-08
  • 1970-01-01
  • 2011-09-18
相关资源
最近更新 更多