使用QSharedDataPointer实现私有数据的正确拷贝运算符,避免拷贝销毁后崩溃
问题描述
我需要为包含QSharedDataPointer指向私有数据的类实现拷贝运算符,当前实现仅在不删除任一拷贝时正常工作。实际情况与预期不符,拷贝指针时引用计数未增加。观察到代码调用的是移动赋值运算符QSharedDataPointer<T> &QSharedDataPointer::operator=(QSharedDataPointer<T> &&other),但我需要调用的是const引用版本QSharedDataPointer<T> &QSharedDataPointer::operator=(const QSharedDataPointer<T> &o),尝试强制转换也没成功。
相关代码
main.cpp
#include <QtDebug> #include "Nix.h" int main( int , char ** ) { Nix *nix1 = new Nix(1, "Hello, I'm one"); Nix *nix2 = new Nix(); nix2 = nix1; nix2->add(2, "Hello, I'm two"); Nix *nix3 = new Nix(*nix2); nix3->add(3, "Hello, I'm three"); qWarning() << "nix1: " << nix2->display(1) << nix1->display(2) << nix1->display(3); qWarning() << "nix2: " << nix2->display(1) << nix2->display(2) << nix1->display(3); delete nix2; // 如果删除nix1,访问nix2会触发段错误 qWarning() << "nix3: " << nix3->display(1) << nix3->display(2) << nix3->display(3); // 触发段错误: qWarning() << "nix1: " << nix1->display(1) << nix1->display(2) << nix1->display(3); return 0; }
Nix.h
#include <QString> #include <QSharedDataPointer> class NixPrivate; class Nix { public: Nix(); Nix(int a, QString b); ~Nix(); Nix(Nix const &other); Nix &operator=(const Nix &other); QString display(int x); void add(int a, QString b); private: QSharedDataPointer<NixPrivate> d; };
Nix.cpp
#include "Nix.h" #include <QMap> #include <QString> #include <QSharedData> class NixPrivate : public QSharedData { public: QMap<int, QString> myMap; }; Nix::Nix() : d(new NixPrivate()) { } Nix::Nix(int a, QString b) : Nix() { d->myMap[a] = b; } Nix::~Nix() { } Nix::Nix(Nix const &other) : Nix() { d = other.d; } Nix &Nix::operator=(const Nix &other) { d = other.d; return *this; } QString Nix::display(int x) { if (d->myMap.contains(x)) { return d->myMap.value(x); } else { return "not found"; } } void Nix::add(int a, QString b) { d->myMap[a] = b; }
问题分析与解决方案
核心错误:指针直接赋值而非对象赋值
你代码里的nix2 = nix1;是指针赋值,不是调用Nix类的赋值运算符!这会让nix2指针直接指向nix1所指向的对象,原来nix2创建的对象内存泄漏,且两个指针指向同一个Nix实例。此时删除nix2就等于删除了nix1指向的对象,后续访问nix1自然触发段错误,和QSharedDataPointer的引用计数无关。
修正步骤
- 修正main中的赋值逻辑:把指针赋值改成对象赋值,即
*nix2 = *nix1;,这样才会调用你实现的operator=,让QSharedDataPointer正确增加引用计数。 - 可选:移除手动实现的拷贝构造和赋值运算符:
QSharedDataPointer本身已经实现了正确的拷贝语义(拷贝时自动增加引用计数),Qt会为Nix类生成默认的拷贝构造和赋值运算符,完全可以删掉你手动写的版本,避免冗余代码。
修正后的main.cpp关键部分
int main( int , char ** ) { Nix *nix1 = new Nix(1, "Hello, I'm one"); Nix *nix2 = new Nix(); // 修正:对象赋值,调用Nix::operator= *nix2 = *nix1; nix2->add(2, "Hello, I'm two"); Nix *nix3 = new Nix(*nix2); nix3->add(3, "Hello, I'm three"); qWarning() << "nix1: " << nix1->display(1) << nix1->display(2) << nix1->display(3); qWarning() << "nix2: " << nix2->display(1) << nix2->display(2) << nix2->display(3); delete nix2; // 此时只是减少引用计数,不会销毁共享数据,因为nix1和nix3还持有引用 qWarning() << "nix3: " << nix3->display(1) << nix3->display(2) << nix3->display(3); qWarning() << "nix1: " << nix1->display(1) << nix1->display(2) << nix1->display(3); // 不再触发段错误 delete nix1; delete nix3; return 0; }
简化后的Nix.h(移除冗余的拷贝构造和赋值声明)
#include <QString> #include <QSharedDataPointer> class NixPrivate; class Nix { public: Nix(); Nix(int a, QString b); ~Nix(); // 移除手动声明的拷贝构造和赋值运算符,Qt会生成默认版本 QString display(int x); void add(int a, QString b); private: QSharedDataPointer<NixPrivate> d; };
简化后的Nix.cpp(移除手动实现的拷贝构造和赋值)
#include "Nix.h" #include <QMap> #include <QString> #include <QSharedData> class NixPrivate : public QSharedData { public: QMap<int, QString> myMap; }; Nix::Nix() : d(new NixPrivate()) { } Nix::Nix(int a, QString b) : Nix() { d->myMap[a] = b; } Nix::~Nix() { } // 移除手动实现的拷贝构造和operator= QString Nix::display(int x) { return d->myMap.contains(x) ? d->myMap.value(x) : "not found"; } void Nix::add(int a, QString b) { d->myMap[a] = b; }
内容的提问来源于stack exchange,提问作者Jodeli
相关产品推荐
相关产品推荐

