move 与 析构函数

第一次尝试使用std::move优化代码,就遇到了坑

简化代码如下:

class moveTest
{
public:
    moveTest() { std::cout << "construct moveTest" << std::endl; };
    ~moveTest() {
        if ((buf) != NULL) delete[](buf); (buf) = NULL;
        std::cout << "delete moveTest" << std::endl;
    };
    void initbuf() { buf = new int[10]; };
private:
	int* buf = nullptr;
};

void push(moveTest& m)
{
	moveTest testA = std::move(m);
};

int main()
{	moveTest b;
	b.initbuf();
	push(b);
	return 0;
} 

上述代码运行时,报错。错误原因 moveTest对象析构出发断点

解释如下:
根据您提供的代码,错误原因是因为在A类中的push函数中,将moveTest对象m移动赋值给testA成员变量时,会调用moveTest类的移动赋值运算符。

然而,根据您提供的代码,moveTest类没有显式定义移动赋值运算符。这会导致使用默认的移动赋值运算符,而默认的移动赋值运算符会简单地将成员变量逐个移动赋值,而不会处理指针成员变量。

moveTest类中,存在一个指针成员变量buf,在默认的移动赋值运算符中,只会简单地将指针成员变量复制到目标对象中。这会导致两个对象的buf指向同一块内存,当其中一个对象被销毁时,会释放内存,导致另一个对象的buf成为悬空指针。

因此,当moveTest对象mpush函数中被移动赋值给testA成员变量后,m对象的析构函数被调用,尝试释放内存,但由于buf成为悬空指针,导致触发断点。

要解决这个问题,您可以在moveTest类中显式定义移动赋值运算符,确保正确处理指针成员变量的移动赋值。例如:

moveTest& operator=(moveTest&& other)
{
    if (this != &other)
    {
        delete[] buf;
        buf = other.buf;
        other.buf = nullptr;
    }
    return *this;
}

通过显式定义移动赋值运算符,确保在移动赋值时正确处理buf指针,避免悬空指针的问题。这样,当moveTest对象被移动赋值时,不会触发断点。

悬空指针是指指向已释放或无效的内存的指针。当我们尝试使用悬空指针时,就会导致未定义行为,这可能包括访问无效的内存、触发段错误、导致程序崩溃等。

在您的代码中,当moveTest对象mpush函数中被移动赋值给testA成员变量后,m对象的析构函数被调用。在析构函数中,您尝试释放指针成员变量buf所指向的内存:

~moveTest() { 
    if ((buf) != NULL) delete[](buf); (buf) = NULL; 
    std::cout << "delete moveTest" << std::endl;
};

然而,在移动赋值运算符中,您没有将buf指针设置为nullptr,而是将其赋值给了目标对象,导致m对象的buf成为悬空指针。因此,在析构函数中尝试释放悬空指针时,会触发断点。

触发断点的具体原因可能是由于调试器检测到了悬空指针的释放,而断点是调试器的一种行为,用于提醒开发人员可能存在的问题。

为了解决这个问题,需要在移动赋值运算符中将buf指针设置为nullptr,以避免在析构函数中释放悬空指针。

正确代码如下:

class moveTest
{
public:
    moveTest() { std::cout << "construct moveTest" << std::endl; };
    ~moveTest() {
        if ((buf) != NULL) delete[](buf); (buf) = NULL;
        std::cout << "delete moveTest" << std::endl;
    };
    void initbuf() { buf = new int[10]; };


    // 移动构造函数
    moveTest(moveTest&& other) noexcept {
        buf = other.buf;
        other.buf = nullptr;
    }

    // 移动赋值运算符
    moveTest& operator=(moveTest&& other) noexcept {
        if (this != &other) {
            delete[] buf;
            buf = other.buf;
            other.buf = nullptr;
        }
        return *this;
    }



private:
	int* buf = nullptr;
};

为了保证 buf 在移动构造函数中被正确处理,需要在移动构造函数中将源对象的 buf 指针设置为 nullptr,并将资源所有权转移给新对象。以下是一个示例:

class moveTest {
public:
    moveTest() { std::cout << "construct moveTest" << std::endl; };
    ~moveTest() { 
        if (buf != nullptr) delete[] buf; 
        std::cout << "delete moveTest" << std::endl;
    };
    void initbuf() { 
        buf = new int[10]; 
    };

    // 自定义移动构造函数
    moveTest(moveTest&& other) noexcept : buf(other.buf) {
        std::cout << "moveTest move constructor called" << std::endl;
        other.buf = nullptr; // 将源对象的 buf 指针设置为 nullptr
    }

private:
    int* buf = nullptr;
    int a, b, c, d, e, f;
};

在上述代码中,我们在 moveTest 类中添加了一个自定义的移动构造函数。在移动构造函数中,我们将源对象的 buf 指针赋值给新对象的 buf 指针,并将源对象的 buf 指针设置为 nullptr,以确保资源所有权正确转移。

以下是一个使用自定义移动构造函数的示例:

int main() {
    moveTest obj1;
    obj1.initbuf();

    moveTest obj2(std::move(obj1)); // 使用移动构造函数将 obj1 的值移动到 obj2 中

    return 0;
}

在上述代码中,我们首先创建了一个名为 obj1moveTest 对象,并调用了 initbuf 方法来初始化 buf 数组。然后,我们使用移动构造函数将 obj1 的值移动到 obj2 中。在移动构造函数中,obj1buf 指针被设置为 nullptr,而 obj2buf 指针指向了原来的 buf 数组。最后,当程序结束时,会调用 obj1obj2 的析构函数,释放 buf 数组的内存。

运行上述代码,将会输出以下内容:

construct moveTest
moveTest move constructor called
delete moveTest
delete moveTest

这表明自定义的移动构造函数成功地将 buf 的所有权从 obj1 转移到了 obj2,并正确地处理了 buf 的释放。

在自定义移动构造函数中,成员变量 a、b 和 c 不需要显式处理,因为它们是基本类型(如 int)的成员变量,可以直接进行移动。当对象被移动时,这些基本类型的成员变量会自动从源对象移动到目标对象中。

在移动构造函数中,只需要处理拥有资源所有权的成员变量,例如指针或动态分配的内存。对于基本类型的成员变量,编译器会自动处理移动操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ChitGPT: 好的,我理解了你的问题。以下是股票类的定义: ```cpp class Stock { public: Stock(std::string name, int count, float price); Stock(const Stock& other); ~Stock(); void add(int count); void reduce(int count); float getTotalPrice() const; private: std::string mName; // 公司名称 int mCount; // 持有股票数量 float mPrice; // 每股价格 float mTotalPrice; // 总价格 // 内联函数,计算总价值 inline void calculateTotalPrice() { mTotalPrice = mCount * mPrice; } }; // 函数,初始化初始数据 Stock::Stock(std::string name, int count, float price) : mName(std::move(name)), mCount(count), mPrice(price) { calculateTotalPrice(); } // 拷贝函数 Stock::Stock(const Stock& other) : mName(other.mName), mCount(other.mCount), mPrice(other.mPrice), mTotalPrice(other.mTotalPrice) {} // 函数 Stock::~Stock() {} // 增加持有股票 void Stock::add(int count) { mCount += count; calculateTotalPrice(); } // 减少持有股票 void Stock::reduce(int count) { if (count > mCount) { std::cerr << "Error: reduce count exceeds owned stock count!" << std::endl; return; } mCount -= count; calculateTotalPrice(); } // 获取总价值 float Stock::getTotalPrice() const { return mTotalPrice; } ``` 这个股票类中包含了公司名称、持有股票数量、每股价格、总价格这些数据成员,并提供了增加持有股票、减少持有股票的成员函数。同时,还有函数函数、内联函数等特性。 希望我的回答可以帮助到你。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值